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 },
124
125 InvalidReturnType { expected: String, actual: String },
129 InvalidArgument {
132 param: String,
133 fn_name: String,
134 expected: String,
135 actual: String,
136 },
137 TooFewArguments {
140 fn_name: String,
141 expected: usize,
142 actual: usize,
143 },
144 TooManyArguments {
147 fn_name: String,
148 expected: usize,
149 actual: usize,
150 },
151 InvalidNamedArgument { fn_name: String, name: String },
154 InvalidNamedArguments { fn_name: String },
157 InvalidPassByReference { fn_name: String, param: String },
160 InvalidPropertyFetch { ty: String },
163 InvalidArrayAccess { ty: String },
166 InvalidArrayAssignment { ty: String },
169 InvalidPropertyAssignment {
172 property: String,
173 expected: String,
174 actual: String,
175 },
176 InvalidCast { from: String, to: String },
179 InvalidStaticInvocation { class: String, method: String },
182 InvalidOperand {
186 op: String,
187 left: String,
188 right: String,
189 },
190 PossiblyInvalidOperand {
193 op: String,
194 left: String,
195 right: String,
196 },
197 PossiblyNullOperand { op: String, ty: String },
200 RawObjectIteration { ty: String },
203 PossiblyRawObjectIteration { ty: String },
206 MismatchingDocblockReturnType { declared: String, inferred: String },
208 MismatchingDocblockParamType {
210 param: String,
211 declared: String,
212 inferred: String,
213 },
214 TypeCheckMismatch {
217 var: String,
218 expected: String,
219 actual: String,
220 },
221
222 Trace { variable: String, type_info: String },
225
226 InvalidArrayOffset { expected: String, actual: String },
229 NonExistentArrayOffset { key: String },
231 PossiblyInvalidArrayOffset { expected: String, actual: String },
234
235 RedundantCondition { ty: String },
239 RedundantCast { from: String, to: String },
242 UnnecessaryVarAnnotation { var: String },
244 TypeDoesNotContainType { left: String, right: String },
246 ParadoxicalCondition { value: String },
249
250 UnusedVariable { name: String },
254 UnusedParam { name: String },
257 UnreachableCode,
260 UnhandledMatchCondition { detail: String },
263 UnusedMethod { class: String, method: String },
266 UnusedProperty { class: String, property: String },
269 UnusedFunction { name: String },
272 UnusedForeachValue { name: String },
275
276 ReadonlyPropertyAssignment { class: String, property: String },
280
281 UnimplementedAbstractMethod { class: String, method: String },
285 UnimplementedInterfaceMethod {
288 class: String,
289 interface: String,
290 method: String,
291 },
292 MethodSignatureMismatch {
295 class: String,
296 method: String,
297 detail: String,
298 },
299 OverriddenMethodAccess { class: String, method: String },
302 OverriddenPropertyAccess { class: String, property: String },
305 DirectConstructorCall { class: String },
308 InvalidExtendClass { parent: String, child: String },
311 FinalMethodOverridden {
314 class: String,
315 method: String,
316 parent: String,
317 },
318 AbstractInstantiation { class: String },
321 AbstractMethodCall { class: String, method: String },
324 InterfaceInstantiation { class: String },
327 InvalidOverride {
331 class: String,
332 method: String,
333 detail: String,
334 },
335
336 TaintedInput { sink: String },
340 TaintedHtml,
343 TaintedSql,
346 TaintedShell,
349
350 InvalidTemplateParam {
354 name: String,
355 expected_bound: String,
356 actual: String,
357 },
358 ShadowedTemplateParam { name: String },
361
362 DeprecatedCall {
366 name: String,
367 message: Option<Arc<str>>,
368 },
369 DeprecatedProperty {
372 class: String,
373 property: String,
374 message: Option<Arc<str>>,
375 },
376 DeprecatedConstant {
379 class: String,
380 constant: String,
381 message: Option<Arc<str>>,
382 },
383 DeprecatedInterface {
386 name: String,
387 message: Option<Arc<str>>,
388 },
389 DeprecatedTrait {
392 name: String,
393 message: Option<Arc<str>>,
394 },
395 DeprecatedMethodCall {
398 class: String,
399 method: String,
400 message: Option<Arc<str>>,
401 },
402 DeprecatedMethod {
405 class: String,
406 method: String,
407 message: Option<Arc<str>>,
408 },
409 DeprecatedClass {
412 name: String,
413 message: Option<Arc<str>>,
414 },
415 InternalMethod { class: String, method: String },
418 MissingReturnType { fn_name: String },
420 MissingParamType { fn_name: String, param: String },
422 MissingPropertyType { class: String, property: String },
425 InvalidThrow { ty: String },
428 InvalidCatch { ty: String },
431 MissingThrowsDocblock { class: String },
434 ImplicitToStringCast { class: String },
437 ImplicitFloatToIntCast { from: String },
440 ParseError { message: String },
443 InvalidDocblock { message: String },
446 MixedArgument { param: String, fn_name: String },
448 MixedAssignment { var: String },
450 MixedMethodCall { method: String },
453 MixedPropertyFetch { property: String },
456 MixedPropertyAssignment { property: String },
459 MixedArrayAccess,
462 MixedArrayOffset,
465 MixedClone,
468 InvalidClone { ty: String },
472 PossiblyInvalidClone { ty: String },
476 InvalidToString { class: String },
480 CircularInheritance { class: String },
483
484 InvalidTraitUse { trait_name: String, reason: String },
488 ForbiddenCode { message: String },
491
492 InvalidAttribute { message: String },
496 UndefinedAttributeClass { name: String },
499
500 WrongCaseFunction { used: String, canonical: String },
504 WrongCaseMethod {
507 class: String,
508 used: String,
509 canonical: String,
510 },
511 WrongCaseClass { used: String, canonical: String },
514 DuplicateClass { name: String },
517 DuplicateInterface { name: String },
520 DuplicateTrait { name: String },
523 DuplicateEnum { name: String },
526 DuplicateFunction { name: String },
529}
530
531fn append_deprecation_message(base: String, message: &Option<Arc<str>>) -> String {
532 match message.as_deref().filter(|m| !m.is_empty()) {
533 Some(msg) => format!("{base}: {msg}"),
534 None => base,
535 }
536}
537
538impl IssueKind {
539 pub fn default_severity(&self) -> Severity {
541 match self {
542 IssueKind::NonStaticSelfCall { .. }
544 | IssueKind::DirectConstructorCall { .. }
545 | IssueKind::InvalidScope { .. }
546 | IssueKind::UndefinedVariable { .. }
547 | IssueKind::UndefinedFunction { .. }
548 | IssueKind::UndefinedMethod { .. }
549 | IssueKind::UndefinedClass { .. }
550 | IssueKind::UndefinedConstant { .. }
551 | IssueKind::InaccessibleClassConstant { .. }
552 | IssueKind::InvalidReturnType { .. }
553 | IssueKind::InvalidArgument { .. }
554 | IssueKind::TooFewArguments { .. }
555 | IssueKind::TooManyArguments { .. }
556 | IssueKind::InvalidNamedArgument { .. }
557 | IssueKind::InvalidNamedArguments { .. }
558 | IssueKind::InvalidPassByReference { .. }
559 | IssueKind::InvalidThrow { .. }
560 | IssueKind::InvalidCatch { .. }
561 | IssueKind::InvalidStaticInvocation { .. }
562 | IssueKind::UnimplementedAbstractMethod { .. }
563 | IssueKind::UnimplementedInterfaceMethod { .. }
564 | IssueKind::MethodSignatureMismatch { .. }
565 | IssueKind::InvalidExtendClass { .. }
566 | IssueKind::FinalMethodOverridden { .. }
567 | IssueKind::AbstractInstantiation { .. }
568 | IssueKind::AbstractMethodCall { .. }
569 | IssueKind::InterfaceInstantiation { .. }
570 | IssueKind::InvalidOverride { .. }
571 | IssueKind::InvalidTemplateParam { .. }
572 | IssueKind::ReadonlyPropertyAssignment { .. }
573 | IssueKind::ParseError { .. }
574 | IssueKind::TaintedInput { .. }
575 | IssueKind::TaintedHtml
576 | IssueKind::TaintedSql
577 | IssueKind::TaintedShell
578 | IssueKind::CircularInheritance { .. }
579 | IssueKind::InvalidTraitUse { .. }
580 | IssueKind::UndefinedTrait { .. }
581 | IssueKind::InvalidClone { .. }
582 | IssueKind::InvalidToString { .. }
583 | IssueKind::TypeCheckMismatch { .. }
584 | IssueKind::ParentNotFound => Severity::Error,
585 IssueKind::Trace { .. } => Severity::Info,
586
587 IssueKind::NullArgument { .. }
589 | IssueKind::NullPropertyFetch { .. }
590 | IssueKind::NullMethodCall { .. }
591 | IssueKind::NullArrayAccess
592 | IssueKind::NullableReturnStatement { .. }
593 | IssueKind::InvalidPropertyFetch { .. }
594 | IssueKind::InvalidArrayAccess { .. }
595 | IssueKind::InvalidArrayAssignment { .. }
596 | IssueKind::InvalidPropertyAssignment { .. }
597 | IssueKind::InvalidArrayOffset { .. }
598 | IssueKind::NonExistentArrayOffset { .. }
599 | IssueKind::PossiblyInvalidArrayOffset { .. }
600 | IssueKind::UndefinedProperty { .. }
601 | IssueKind::InvalidOperand { .. }
602 | IssueKind::OverriddenMethodAccess { .. }
603 | IssueKind::OverriddenPropertyAccess { .. }
604 | IssueKind::ImplicitToStringCast { .. }
605 | IssueKind::ImplicitFloatToIntCast { .. }
606 | IssueKind::UnusedVariable { .. }
607 | IssueKind::UnusedForeachValue { .. }
608 | IssueKind::ParadoxicalCondition { .. }
609 | IssueKind::UnhandledMatchCondition { .. }
610 | IssueKind::InvalidStringClass { .. }
611 | IssueKind::ForbiddenCode { .. } => Severity::Warning,
612
613 IssueKind::PossiblyUndefinedVariable { .. } => Severity::Warning,
615
616 IssueKind::PossiblyNullArgument { .. }
618 | IssueKind::PossiblyInvalidArgument { .. }
619 | IssueKind::PossiblyNullPropertyFetch { .. }
620 | IssueKind::PossiblyNullMethodCall { .. }
621 | IssueKind::PossiblyNullArrayAccess
622 | IssueKind::PossiblyInvalidClone { .. }
623 | IssueKind::PossiblyInvalidOperand { .. }
624 | IssueKind::PossiblyNullOperand { .. }
625 | IssueKind::PossiblyRawObjectIteration { .. } => Severity::Info,
626
627 IssueKind::RawObjectIteration { .. } => Severity::Warning,
628
629 IssueKind::RedundantCondition { .. }
631 | IssueKind::RedundantCast { .. }
632 | IssueKind::UnnecessaryVarAnnotation { .. }
633 | IssueKind::TypeDoesNotContainType { .. }
634 | IssueKind::UnusedParam { .. }
635 | IssueKind::UnreachableCode
636 | IssueKind::UnusedMethod { .. }
637 | IssueKind::UnusedProperty { .. }
638 | IssueKind::UnusedFunction { .. }
639 | IssueKind::DeprecatedCall { .. }
640 | IssueKind::DeprecatedProperty { .. }
641 | IssueKind::DeprecatedConstant { .. }
642 | IssueKind::DeprecatedInterface { .. }
643 | IssueKind::DeprecatedTrait { .. }
644 | IssueKind::DeprecatedMethodCall { .. }
645 | IssueKind::DeprecatedMethod { .. }
646 | IssueKind::DeprecatedClass { .. }
647 | IssueKind::InternalMethod { .. }
648 | IssueKind::MissingReturnType { .. }
649 | IssueKind::MissingParamType { .. }
650 | IssueKind::MissingPropertyType { .. }
651 | IssueKind::MismatchingDocblockReturnType { .. }
652 | IssueKind::MismatchingDocblockParamType { .. }
653 | IssueKind::InvalidDocblock { .. }
654 | IssueKind::InvalidCast { .. }
655 | IssueKind::MixedArgument { .. }
656 | IssueKind::MixedAssignment { .. }
657 | IssueKind::MixedMethodCall { .. }
658 | IssueKind::MixedPropertyFetch { .. }
659 | IssueKind::MixedPropertyAssignment { .. }
660 | IssueKind::MixedArrayAccess
661 | IssueKind::MixedArrayOffset
662 | IssueKind::MixedClone
663 | IssueKind::ShadowedTemplateParam { .. }
664 | IssueKind::MissingThrowsDocblock { .. }
665 | IssueKind::WrongCaseFunction { .. }
666 | IssueKind::WrongCaseMethod { .. }
667 | IssueKind::WrongCaseClass { .. }
668 | IssueKind::InvalidAttribute { .. }
669 | IssueKind::UndefinedAttributeClass { .. } => Severity::Info,
670 IssueKind::DuplicateClass { .. }
671 | IssueKind::DuplicateInterface { .. }
672 | IssueKind::DuplicateTrait { .. }
673 | IssueKind::DuplicateEnum { .. }
674 | IssueKind::DuplicateFunction { .. } => Severity::Error,
675 }
676 }
677
678 pub fn code(&self) -> &'static str {
706 match self {
707 IssueKind::NonStaticSelfCall { .. } => "MIR0216",
709 IssueKind::DirectConstructorCall { .. } => "MIR0217",
710 IssueKind::InvalidScope { .. } => "MIR0001",
711 IssueKind::UndefinedVariable { .. } => "MIR0002",
712 IssueKind::UndefinedFunction { .. } => "MIR0003",
713 IssueKind::UndefinedMethod { .. } => "MIR0004",
714 IssueKind::UndefinedClass { .. } => "MIR0005",
715 IssueKind::UndefinedProperty { .. } => "MIR0006",
716 IssueKind::UndefinedConstant { .. } => "MIR0007",
717 IssueKind::InaccessibleClassConstant { .. } => "MIR0011",
718 IssueKind::PossiblyUndefinedVariable { .. } => "MIR0008",
719 IssueKind::UndefinedTrait { .. } => "MIR0009",
720 IssueKind::ParentNotFound => "MIR0010",
721
722 IssueKind::NullArgument { .. } => "MIR0100",
724 IssueKind::NullPropertyFetch { .. } => "MIR0101",
725 IssueKind::NullMethodCall { .. } => "MIR0102",
726 IssueKind::NullArrayAccess => "MIR0103",
727 IssueKind::PossiblyNullArgument { .. } => "MIR0104",
728 IssueKind::PossiblyInvalidArgument { .. } => "MIR0105",
729 IssueKind::PossiblyNullPropertyFetch { .. } => "MIR0106",
730 IssueKind::PossiblyNullMethodCall { .. } => "MIR0107",
731 IssueKind::PossiblyNullArrayAccess => "MIR0108",
732 IssueKind::NullableReturnStatement { .. } => "MIR0109",
733
734 IssueKind::InvalidReturnType { .. } => "MIR0200",
736 IssueKind::InvalidArgument { .. } => "MIR0201",
737 IssueKind::TooFewArguments { .. } => "MIR0202",
738 IssueKind::TooManyArguments { .. } => "MIR0203",
739 IssueKind::InvalidNamedArgument { .. } => "MIR0204",
740 IssueKind::InvalidNamedArguments { .. } => "MIR0224",
741 IssueKind::InvalidPassByReference { .. } => "MIR0205",
742 IssueKind::InvalidPropertyFetch { .. } => "MIR0218",
743 IssueKind::InvalidArrayAccess { .. } => "MIR0219",
744 IssueKind::InvalidArrayAssignment { .. } => "MIR0220",
745 IssueKind::InvalidPropertyAssignment { .. } => "MIR0206",
746 IssueKind::InvalidCast { .. } => "MIR0207",
747 IssueKind::InvalidStaticInvocation { .. } => "MIR0215",
748 IssueKind::InvalidOperand { .. } => "MIR0208",
749 IssueKind::PossiblyInvalidOperand { .. } => "MIR0213",
750 IssueKind::PossiblyNullOperand { .. } => "MIR0214",
751 IssueKind::RawObjectIteration { .. } => "MIR0222",
752 IssueKind::PossiblyRawObjectIteration { .. } => "MIR0223",
753 IssueKind::MismatchingDocblockReturnType { .. } => "MIR0209",
754 IssueKind::MismatchingDocblockParamType { .. } => "MIR0210",
755 IssueKind::InvalidStringClass { .. } => "MIR0211",
756 IssueKind::TypeCheckMismatch { .. } => "MIR0212",
757 IssueKind::Trace { .. } => "MIR0221",
758
759 IssueKind::InvalidArrayOffset { .. } => "MIR0300",
761 IssueKind::NonExistentArrayOffset { .. } => "MIR0301",
762 IssueKind::PossiblyInvalidArrayOffset { .. } => "MIR0302",
763
764 IssueKind::RedundantCondition { .. } => "MIR0400",
766 IssueKind::RedundantCast { .. } => "MIR0401",
767 IssueKind::UnnecessaryVarAnnotation { .. } => "MIR0402",
768 IssueKind::TypeDoesNotContainType { .. } => "MIR0403",
769 IssueKind::ParadoxicalCondition { .. } => "MIR0404",
770 IssueKind::UnhandledMatchCondition { .. } => "MIR0405",
771
772 IssueKind::UnusedVariable { .. } => "MIR0500",
774 IssueKind::UnusedParam { .. } => "MIR0501",
775 IssueKind::UnreachableCode => "MIR0502",
776 IssueKind::UnusedMethod { .. } => "MIR0503",
777 IssueKind::UnusedProperty { .. } => "MIR0504",
778 IssueKind::UnusedFunction { .. } => "MIR0505",
779 IssueKind::UnusedForeachValue { .. } => "MIR0506",
780
781 IssueKind::ReadonlyPropertyAssignment { .. } => "MIR0600",
783
784 IssueKind::UnimplementedAbstractMethod { .. } => "MIR0700",
786 IssueKind::UnimplementedInterfaceMethod { .. } => "MIR0701",
787 IssueKind::MethodSignatureMismatch { .. } => "MIR0702",
788 IssueKind::OverriddenMethodAccess { .. } => "MIR0703",
789 IssueKind::OverriddenPropertyAccess { .. } => "MIR0710",
790 IssueKind::InvalidExtendClass { .. } => "MIR0704",
791 IssueKind::FinalMethodOverridden { .. } => "MIR0705",
792 IssueKind::AbstractInstantiation { .. } => "MIR0706",
793 IssueKind::AbstractMethodCall { .. } => "MIR0711",
794 IssueKind::InterfaceInstantiation { .. } => "MIR0709",
795 IssueKind::CircularInheritance { .. } => "MIR0707",
796 IssueKind::InvalidOverride { .. } => "MIR0708",
797
798 IssueKind::TaintedInput { .. } => "MIR0800",
800 IssueKind::TaintedHtml => "MIR0801",
801 IssueKind::TaintedSql => "MIR0802",
802 IssueKind::TaintedShell => "MIR0803",
803
804 IssueKind::InvalidTemplateParam { .. } => "MIR0900",
806 IssueKind::ShadowedTemplateParam { .. } => "MIR0901",
807
808 IssueKind::DeprecatedCall { .. } => "MIR1000",
810 IssueKind::WrongCaseFunction { .. } => "MIR1009",
811 IssueKind::WrongCaseMethod { .. } => "MIR1010",
812 IssueKind::WrongCaseClass { .. } => "MIR1011",
813 IssueKind::DeprecatedProperty { .. } => "MIR1005",
814 IssueKind::DeprecatedInterface { .. } => "MIR1006",
815 IssueKind::DeprecatedTrait { .. } => "MIR1007",
816 IssueKind::DeprecatedConstant { .. } => "MIR1008",
817 IssueKind::DeprecatedMethodCall { .. } => "MIR1001",
818 IssueKind::DeprecatedMethod { .. } => "MIR1002",
819 IssueKind::DeprecatedClass { .. } => "MIR1003",
820 IssueKind::InternalMethod { .. } => "MIR1004",
821
822 IssueKind::MissingReturnType { .. } => "MIR1100",
824 IssueKind::MissingParamType { .. } => "MIR1101",
825 IssueKind::MissingPropertyType { .. } => "MIR1104",
826 IssueKind::MissingThrowsDocblock { .. } => "MIR1102",
827 IssueKind::InvalidDocblock { .. } => "MIR1103",
828
829 IssueKind::MixedArgument { .. } => "MIR1200",
831 IssueKind::MixedAssignment { .. } => "MIR1201",
832 IssueKind::MixedMethodCall { .. } => "MIR1202",
833 IssueKind::MixedPropertyFetch { .. } => "MIR1203",
834 IssueKind::MixedPropertyAssignment { .. } => "MIR1208",
835 IssueKind::MixedArrayAccess => "MIR1209",
836 IssueKind::MixedArrayOffset => "MIR1210",
837 IssueKind::MixedClone => "MIR1204",
838 IssueKind::InvalidClone { .. } => "MIR1205",
839 IssueKind::PossiblyInvalidClone { .. } => "MIR1206",
840 IssueKind::InvalidToString { .. } => "MIR1207",
841
842 IssueKind::InvalidTraitUse { .. } => "MIR1300",
844 IssueKind::ForbiddenCode { .. } => "MIR1301",
845
846 IssueKind::ParseError { .. } => "MIR1400",
848
849 IssueKind::InvalidAttribute { .. } => "MIR1600",
851 IssueKind::UndefinedAttributeClass { .. } => "MIR1601",
852 IssueKind::DuplicateClass { .. } => "MIR1602",
853 IssueKind::DuplicateInterface { .. } => "MIR1603",
854 IssueKind::DuplicateTrait { .. } => "MIR1604",
855 IssueKind::DuplicateEnum { .. } => "MIR1605",
856 IssueKind::DuplicateFunction { .. } => "MIR1606",
857
858 IssueKind::InvalidThrow { .. } => "MIR1500",
860 IssueKind::InvalidCatch { .. } => "MIR1503",
861 IssueKind::ImplicitToStringCast { .. } => "MIR1501",
862 IssueKind::ImplicitFloatToIntCast { .. } => "MIR1502",
863 }
864 }
865
866 pub fn default_severity_for_code(code: &str) -> Option<Severity> {
872 match code {
873 "MIR0001" | "MIR0002" | "MIR0003" | "MIR0004" | "MIR0005" | "MIR0007" | "MIR0009"
875 | "MIR0010" | "MIR0011" | "MIR0200" | "MIR0201" | "MIR0202" | "MIR0203" | "MIR0204"
876 | "MIR0205" | "MIR0212" | "MIR0215" | "MIR0216" | "MIR0217" | "MIR0224" | "MIR0600"
877 | "MIR0700" | "MIR0701" | "MIR0702" | "MIR0704" | "MIR0705" | "MIR0706" | "MIR0707"
878 | "MIR0708" | "MIR0709" | "MIR0711" | "MIR0800" | "MIR0801" | "MIR0802" | "MIR0803"
879 | "MIR0900" | "MIR1205" | "MIR1207" | "MIR1300" | "MIR1400" | "MIR1500" | "MIR1503"
880 | "MIR1602" | "MIR1603" | "MIR1604" | "MIR1605" | "MIR1606" => Some(Severity::Error),
881
882 "MIR0006" | "MIR0008" | "MIR0100" | "MIR0101" | "MIR0102" | "MIR0103" | "MIR0109"
884 | "MIR0206" | "MIR0208" | "MIR0211" | "MIR0218" | "MIR0219" | "MIR0220" | "MIR0222"
885 | "MIR0300" | "MIR0301" | "MIR0302" | "MIR0404" | "MIR0405" | "MIR0500" | "MIR0506"
886 | "MIR0703" | "MIR0710" | "MIR1301" | "MIR1501" | "MIR1502" => Some(Severity::Warning),
887
888 "MIR0104" | "MIR0105" | "MIR0106" | "MIR0107" | "MIR0108" | "MIR0207" | "MIR0209"
890 | "MIR0210" | "MIR0213" | "MIR0214" | "MIR0221" | "MIR0223" | "MIR0400" | "MIR0401"
891 | "MIR0402" | "MIR0403" | "MIR0501" | "MIR0502" | "MIR0503" | "MIR0504" | "MIR0505"
892 | "MIR0901" | "MIR1000" | "MIR1001" | "MIR1002" | "MIR1003" | "MIR1004" | "MIR1005"
893 | "MIR1006" | "MIR1007" | "MIR1008" | "MIR1009" | "MIR1010" | "MIR1011" | "MIR1100"
894 | "MIR1101" | "MIR1102" | "MIR1103" | "MIR1104" | "MIR1200" | "MIR1201" | "MIR1202"
895 | "MIR1203" | "MIR1204" | "MIR1206" | "MIR1208" | "MIR1209" | "MIR1210" | "MIR1600"
896 | "MIR1601" => Some(Severity::Info),
897
898 _ => None,
899 }
900 }
901
902 pub fn name(&self) -> &'static str {
904 match self {
905 IssueKind::NonStaticSelfCall { .. } => "NonStaticSelfCall",
906 IssueKind::DirectConstructorCall { .. } => "DirectConstructorCall",
907 IssueKind::InvalidScope { .. } => "InvalidScope",
908 IssueKind::UndefinedVariable { .. } => "UndefinedVariable",
909 IssueKind::UndefinedFunction { .. } => "UndefinedFunction",
910 IssueKind::UndefinedMethod { .. } => "UndefinedMethod",
911 IssueKind::UndefinedClass { .. } => "UndefinedClass",
912 IssueKind::UndefinedProperty { .. } => "UndefinedProperty",
913 IssueKind::UndefinedConstant { .. } => "UndefinedConstant",
914 IssueKind::InaccessibleClassConstant { .. } => "InaccessibleClassConstant",
915 IssueKind::PossiblyUndefinedVariable { .. } => "PossiblyUndefinedVariable",
916 IssueKind::UndefinedTrait { .. } => "UndefinedTrait",
917 IssueKind::ParentNotFound => "ParentNotFound",
918 IssueKind::InvalidStringClass { .. } => "InvalidStringClass",
919 IssueKind::NullArgument { .. } => "NullArgument",
920 IssueKind::NullPropertyFetch { .. } => "NullPropertyFetch",
921 IssueKind::NullMethodCall { .. } => "NullMethodCall",
922 IssueKind::NullArrayAccess => "NullArrayAccess",
923 IssueKind::PossiblyNullArgument { .. } => "PossiblyNullArgument",
924 IssueKind::PossiblyInvalidArgument { .. } => "PossiblyInvalidArgument",
925 IssueKind::PossiblyNullPropertyFetch { .. } => "PossiblyNullPropertyFetch",
926 IssueKind::PossiblyNullMethodCall { .. } => "PossiblyNullMethodCall",
927 IssueKind::PossiblyNullArrayAccess => "PossiblyNullArrayAccess",
928 IssueKind::NullableReturnStatement { .. } => "NullableReturnStatement",
929 IssueKind::InvalidReturnType { .. } => "InvalidReturnType",
930 IssueKind::InvalidArgument { .. } => "InvalidArgument",
931 IssueKind::TooFewArguments { .. } => "TooFewArguments",
932 IssueKind::TooManyArguments { .. } => "TooManyArguments",
933 IssueKind::InvalidNamedArgument { .. } => "InvalidNamedArgument",
934 IssueKind::InvalidNamedArguments { .. } => "InvalidNamedArguments",
935 IssueKind::InvalidPassByReference { .. } => "InvalidPassByReference",
936 IssueKind::InvalidPropertyFetch { .. } => "InvalidPropertyFetch",
937 IssueKind::InvalidArrayAccess { .. } => "InvalidArrayAccess",
938 IssueKind::InvalidArrayAssignment { .. } => "InvalidArrayAssignment",
939 IssueKind::InvalidPropertyAssignment { .. } => "InvalidPropertyAssignment",
940 IssueKind::InvalidCast { .. } => "InvalidCast",
941 IssueKind::InvalidStaticInvocation { .. } => "InvalidStaticInvocation",
942 IssueKind::InvalidOperand { .. } => "InvalidOperand",
943 IssueKind::PossiblyInvalidOperand { .. } => "PossiblyInvalidOperand",
944 IssueKind::PossiblyNullOperand { .. } => "PossiblyNullOperand",
945 IssueKind::RawObjectIteration { .. } => "RawObjectIteration",
946 IssueKind::PossiblyRawObjectIteration { .. } => "PossiblyRawObjectIteration",
947 IssueKind::MismatchingDocblockReturnType { .. } => "MismatchingDocblockReturnType",
948 IssueKind::MismatchingDocblockParamType { .. } => "MismatchingDocblockParamType",
949 IssueKind::TypeCheckMismatch { .. } => "TypeCheckMismatch",
950 IssueKind::Trace { .. } => "Trace",
951 IssueKind::InvalidArrayOffset { .. } => "InvalidArrayOffset",
952 IssueKind::NonExistentArrayOffset { .. } => "NonExistentArrayOffset",
953 IssueKind::PossiblyInvalidArrayOffset { .. } => "PossiblyInvalidArrayOffset",
954 IssueKind::RedundantCondition { .. } => "RedundantCondition",
955 IssueKind::RedundantCast { .. } => "RedundantCast",
956 IssueKind::UnnecessaryVarAnnotation { .. } => "UnnecessaryVarAnnotation",
957 IssueKind::TypeDoesNotContainType { .. } => "TypeDoesNotContainType",
958 IssueKind::ParadoxicalCondition { .. } => "ParadoxicalCondition",
959 IssueKind::UnhandledMatchCondition { .. } => "UnhandledMatchCondition",
960 IssueKind::UnusedVariable { .. } => "UnusedVariable",
961 IssueKind::UnusedParam { .. } => "UnusedParam",
962 IssueKind::UnreachableCode => "UnreachableCode",
963 IssueKind::UnusedMethod { .. } => "UnusedMethod",
964 IssueKind::UnusedProperty { .. } => "UnusedProperty",
965 IssueKind::UnusedFunction { .. } => "UnusedFunction",
966 IssueKind::UnusedForeachValue { .. } => "UnusedForeachValue",
967 IssueKind::UnimplementedAbstractMethod { .. } => "UnimplementedAbstractMethod",
968 IssueKind::UnimplementedInterfaceMethod { .. } => "UnimplementedInterfaceMethod",
969 IssueKind::MethodSignatureMismatch { .. } => "MethodSignatureMismatch",
970 IssueKind::OverriddenMethodAccess { .. } => "OverriddenMethodAccess",
971 IssueKind::OverriddenPropertyAccess { .. } => "OverriddenPropertyAccess",
972 IssueKind::InvalidExtendClass { .. } => "InvalidExtendClass",
973 IssueKind::FinalMethodOverridden { .. } => "FinalMethodOverridden",
974 IssueKind::AbstractInstantiation { .. } => "AbstractInstantiation",
975 IssueKind::AbstractMethodCall { .. } => "AbstractMethodCall",
976 IssueKind::InterfaceInstantiation { .. } => "InterfaceInstantiation",
977 IssueKind::InvalidOverride { .. } => "InvalidOverride",
978 IssueKind::ReadonlyPropertyAssignment { .. } => "ReadonlyPropertyAssignment",
979 IssueKind::InvalidTemplateParam { .. } => "InvalidTemplateParam",
980 IssueKind::ShadowedTemplateParam { .. } => "ShadowedTemplateParam",
981 IssueKind::TaintedInput { .. } => "TaintedInput",
982 IssueKind::TaintedHtml => "TaintedHtml",
983 IssueKind::TaintedSql => "TaintedSql",
984 IssueKind::TaintedShell => "TaintedShell",
985 IssueKind::DeprecatedCall { .. } => "DeprecatedCall",
986 IssueKind::DeprecatedProperty { .. } => "DeprecatedProperty",
987 IssueKind::DeprecatedConstant { .. } => "DeprecatedConstant",
988 IssueKind::DeprecatedInterface { .. } => "DeprecatedInterface",
989 IssueKind::DeprecatedTrait { .. } => "DeprecatedTrait",
990 IssueKind::DeprecatedMethodCall { .. } => "DeprecatedMethodCall",
991 IssueKind::DeprecatedMethod { .. } => "DeprecatedMethod",
992 IssueKind::DeprecatedClass { .. } => "DeprecatedClass",
993 IssueKind::InternalMethod { .. } => "InternalMethod",
994 IssueKind::MissingReturnType { .. } => "MissingReturnType",
995 IssueKind::MissingParamType { .. } => "MissingParamType",
996 IssueKind::MissingPropertyType { .. } => "MissingPropertyType",
997 IssueKind::InvalidThrow { .. } => "InvalidThrow",
998 IssueKind::InvalidCatch { .. } => "InvalidCatch",
999 IssueKind::MissingThrowsDocblock { .. } => "MissingThrowsDocblock",
1000 IssueKind::ImplicitToStringCast { .. } => "ImplicitToStringCast",
1001 IssueKind::ImplicitFloatToIntCast { .. } => "ImplicitFloatToIntCast",
1002 IssueKind::ParseError { .. } => "ParseError",
1003 IssueKind::InvalidDocblock { .. } => "InvalidDocblock",
1004 IssueKind::MixedArgument { .. } => "MixedArgument",
1005 IssueKind::MixedAssignment { .. } => "MixedAssignment",
1006 IssueKind::MixedMethodCall { .. } => "MixedMethodCall",
1007 IssueKind::MixedPropertyFetch { .. } => "MixedPropertyFetch",
1008 IssueKind::MixedPropertyAssignment { .. } => "MixedPropertyAssignment",
1009 IssueKind::MixedArrayAccess => "MixedArrayAccess",
1010 IssueKind::MixedArrayOffset => "MixedArrayOffset",
1011 IssueKind::MixedClone => "MixedClone",
1012 IssueKind::InvalidClone { .. } => "InvalidClone",
1013 IssueKind::PossiblyInvalidClone { .. } => "PossiblyInvalidClone",
1014 IssueKind::InvalidToString { .. } => "InvalidToString",
1015 IssueKind::CircularInheritance { .. } => "CircularInheritance",
1016 IssueKind::InvalidTraitUse { .. } => "InvalidTraitUse",
1017 IssueKind::ForbiddenCode { .. } => "ForbiddenCode",
1018 IssueKind::WrongCaseFunction { .. } => "WrongCaseFunction",
1019 IssueKind::WrongCaseMethod { .. } => "WrongCaseMethod",
1020 IssueKind::WrongCaseClass { .. } => "WrongCaseClass",
1021 IssueKind::InvalidAttribute { .. } => "InvalidAttribute",
1022 IssueKind::UndefinedAttributeClass { .. } => "UndefinedAttributeClass",
1023 IssueKind::DuplicateClass { .. } => "DuplicateClass",
1024 IssueKind::DuplicateInterface { .. } => "DuplicateInterface",
1025 IssueKind::DuplicateTrait { .. } => "DuplicateTrait",
1026 IssueKind::DuplicateEnum { .. } => "DuplicateEnum",
1027 IssueKind::DuplicateFunction { .. } => "DuplicateFunction",
1028 }
1029 }
1030
1031 pub fn message(&self) -> String {
1033 match self {
1034 IssueKind::NonStaticSelfCall { class, method } => {
1035 format!("Non-static method {class}::{method}() cannot be called statically")
1036 }
1037 IssueKind::DirectConstructorCall { class } => {
1038 format!("Cannot call constructor of {class} directly")
1039 }
1040 IssueKind::InvalidScope { in_class } => {
1041 if *in_class {
1042 "$this cannot be used in a static method".to_string()
1043 } else {
1044 "$this cannot be used outside of a class".to_string()
1045 }
1046 }
1047 IssueKind::UndefinedVariable { name } => format!("Variable ${name} is not defined"),
1048 IssueKind::UndefinedFunction { name } => format!("Function {name}() is not defined"),
1049 IssueKind::UndefinedMethod { class, method } => {
1050 format!("Method {class}::{method}() does not exist")
1051 }
1052 IssueKind::UndefinedClass { name } => format!("Class {name} does not exist"),
1053 IssueKind::UndefinedProperty { class, property } => {
1054 format!("Property {class}::${property} does not exist")
1055 }
1056 IssueKind::UndefinedConstant { name } => format!("Constant {name} is not defined"),
1057 IssueKind::InaccessibleClassConstant { class, constant } => {
1058 format!("Cannot access constant {class}::{constant}")
1059 }
1060 IssueKind::PossiblyUndefinedVariable { name } => {
1061 format!("Variable ${name} might not be defined")
1062 }
1063 IssueKind::UndefinedTrait { name } => format!("Trait {name} does not exist"),
1064 IssueKind::ParentNotFound => {
1065 "Cannot use parent:: when current class has no parent".to_string()
1066 }
1067 IssueKind::InvalidStringClass { actual } => {
1068 format!("Dynamic class instantiation requires string or class-string type, got '{actual}'")
1069 }
1070
1071 IssueKind::NullArgument { param, fn_name } => {
1072 format!("Argument ${param} of {fn_name}() cannot be null")
1073 }
1074 IssueKind::NullPropertyFetch { property } => {
1075 format!("Cannot access property ${property} on null")
1076 }
1077 IssueKind::NullMethodCall { method } => {
1078 format!("Cannot call method {method}() on null")
1079 }
1080 IssueKind::NullArrayAccess => "Cannot access array on null".to_string(),
1081 IssueKind::PossiblyNullArgument { param, fn_name } => {
1082 format!("Argument ${param} of {fn_name}() might be null")
1083 }
1084 IssueKind::PossiblyInvalidArgument {
1085 param,
1086 fn_name,
1087 expected,
1088 actual,
1089 } => {
1090 format!("Argument ${param} of {fn_name}() expects '{expected}', possibly different type '{actual}' provided")
1091 }
1092 IssueKind::PossiblyNullPropertyFetch { property } => {
1093 format!("Cannot access property ${property} on possibly null value")
1094 }
1095 IssueKind::PossiblyNullMethodCall { method } => {
1096 format!("Cannot call method {method}() on possibly null value")
1097 }
1098 IssueKind::PossiblyNullArrayAccess => {
1099 "Cannot access array on possibly null value".to_string()
1100 }
1101 IssueKind::NullableReturnStatement { expected, actual } => {
1102 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1103 }
1104
1105 IssueKind::InvalidReturnType { expected, actual } => {
1106 format!("Return type '{actual}' is not compatible with declared '{expected}'")
1107 }
1108 IssueKind::InvalidArgument {
1109 param,
1110 fn_name,
1111 expected,
1112 actual,
1113 } => {
1114 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}'")
1115 }
1116 IssueKind::TooFewArguments {
1117 fn_name,
1118 expected,
1119 actual,
1120 } => {
1121 format!(
1122 "Too few arguments for {}(): expected {}, got {}",
1123 fn_name, expected, actual
1124 )
1125 }
1126 IssueKind::TooManyArguments {
1127 fn_name,
1128 expected,
1129 actual,
1130 } => {
1131 format!(
1132 "Too many arguments for {}(): expected {}, got {}",
1133 fn_name, expected, actual
1134 )
1135 }
1136 IssueKind::InvalidNamedArgument { fn_name, name } => {
1137 format!("{}() has no parameter named ${}", fn_name, name)
1138 }
1139 IssueKind::InvalidNamedArguments { fn_name } => {
1140 format!("{}() does not accept named arguments", fn_name)
1141 }
1142 IssueKind::InvalidPassByReference { fn_name, param } => {
1143 format!(
1144 "Argument ${} of {}() must be passed by reference",
1145 param, fn_name
1146 )
1147 }
1148 IssueKind::InvalidPropertyFetch { ty } => {
1149 format!("Cannot fetch property on non-object type '{ty}'")
1150 }
1151 IssueKind::InvalidArrayAccess { ty } => {
1152 format!("Cannot use [] operator on non-array type '{ty}'")
1153 }
1154 IssueKind::InvalidArrayAssignment { ty } => {
1155 format!("Cannot use [] assignment on non-array type '{ty}'")
1156 }
1157 IssueKind::InvalidPropertyAssignment {
1158 property,
1159 expected,
1160 actual,
1161 } => {
1162 format!("Property ${property} expects '{expected}', cannot assign '{actual}'")
1163 }
1164 IssueKind::InvalidCast { from, to } => {
1165 format!("Cannot cast '{from}' to '{to}'")
1166 }
1167 IssueKind::InvalidStaticInvocation { class, method } => {
1168 format!("Non-static method {class}::{method}() cannot be called statically")
1169 }
1170 IssueKind::InvalidOperand { op, left, right } => {
1171 format!("Operator '{op}' not supported between '{left}' and '{right}'")
1172 }
1173 IssueKind::PossiblyInvalidOperand { op, left, right } => {
1174 format!("Operator '{op}' might not be supported between '{left}' and '{right}'")
1175 }
1176 IssueKind::PossiblyNullOperand { op, ty } => {
1177 format!("Operator '{op}' operand '{ty}' might be null")
1178 }
1179 IssueKind::RawObjectIteration { ty } => {
1180 format!("Cannot iterate over non-iterable object '{ty}'")
1181 }
1182 IssueKind::PossiblyRawObjectIteration { ty } => {
1183 format!("Cannot iterate over possibly non-iterable object '{ty}'")
1184 }
1185 IssueKind::MismatchingDocblockReturnType { declared, inferred } => {
1186 format!("Docblock return type '{declared}' does not match inferred '{inferred}'")
1187 }
1188 IssueKind::MismatchingDocblockParamType {
1189 param,
1190 declared,
1191 inferred,
1192 } => {
1193 format!(
1194 "Docblock type '{declared}' for ${param} does not match inferred '{inferred}'"
1195 )
1196 }
1197 IssueKind::TypeCheckMismatch {
1198 var,
1199 expected,
1200 actual,
1201 } => {
1202 format!("Type of ${var} is expected to be {expected}, got {actual}")
1203 }
1204 IssueKind::Trace {
1205 variable,
1206 type_info,
1207 } => {
1208 format!("Type of ${variable} is {type_info}")
1209 }
1210
1211 IssueKind::InvalidArrayOffset { expected, actual } => {
1212 format!("Array offset expects '{expected}', got '{actual}'")
1213 }
1214 IssueKind::NonExistentArrayOffset { key } => {
1215 format!("Array offset '{key}' does not exist")
1216 }
1217 IssueKind::PossiblyInvalidArrayOffset { expected, actual } => {
1218 format!("Array offset might be invalid: expects '{expected}', got '{actual}'")
1219 }
1220
1221 IssueKind::RedundantCondition { ty } => {
1222 format!("Condition is always true/false for type '{ty}'")
1223 }
1224 IssueKind::RedundantCast { from, to } => {
1225 format!("Casting '{from}' to '{to}' is redundant")
1226 }
1227 IssueKind::UnnecessaryVarAnnotation { var } => {
1228 format!("@var annotation for ${var} is unnecessary")
1229 }
1230 IssueKind::TypeDoesNotContainType { left, right } => {
1231 format!("Type '{left}' can never contain type '{right}'")
1232 }
1233 IssueKind::ParadoxicalCondition { value } => {
1234 format!("Value {value} is duplicated; this branch can never be reached")
1235 }
1236 IssueKind::UnhandledMatchCondition { detail } => {
1237 format!("Unhandled match condition: {detail}")
1238 }
1239
1240 IssueKind::UnusedVariable { name } => format!("Variable ${name} is never read"),
1241 IssueKind::UnusedParam { name } => format!("Parameter ${name} is never used"),
1242 IssueKind::UnreachableCode => "Unreachable code detected".to_string(),
1243 IssueKind::UnusedMethod { class, method } => {
1244 format!("Private method {class}::{method}() is never called")
1245 }
1246 IssueKind::UnusedProperty { class, property } => {
1247 format!("Private property {class}::${property} is never read")
1248 }
1249 IssueKind::UnusedFunction { name } => {
1250 format!("Function {name}() is never called")
1251 }
1252 IssueKind::UnusedForeachValue { name } => {
1253 format!("Foreach value ${name} is never read")
1254 }
1255
1256 IssueKind::UnimplementedAbstractMethod { class, method } => {
1257 format!("Class {class} must implement abstract method {method}()")
1258 }
1259 IssueKind::UnimplementedInterfaceMethod {
1260 class,
1261 interface,
1262 method,
1263 } => {
1264 format!("Class {class} must implement {interface}::{method}() from interface")
1265 }
1266 IssueKind::MethodSignatureMismatch {
1267 class,
1268 method,
1269 detail,
1270 } => {
1271 format!("Method {class}::{method}() signature mismatch: {detail}")
1272 }
1273 IssueKind::OverriddenMethodAccess { class, method } => {
1274 format!("Method {class}::{method}() overrides with less visibility")
1275 }
1276 IssueKind::OverriddenPropertyAccess { class, property } => {
1277 format!("Property {class}::${property} overrides with less visibility")
1278 }
1279 IssueKind::ReadonlyPropertyAssignment { class, property } => {
1280 format!(
1281 "Cannot assign to readonly property {class}::${property} outside of constructor"
1282 )
1283 }
1284 IssueKind::InvalidExtendClass { parent, child } => {
1285 format!("Class {child} cannot extend final class {parent}")
1286 }
1287 IssueKind::InvalidTemplateParam {
1288 name,
1289 expected_bound,
1290 actual,
1291 } => {
1292 format!(
1293 "Template type '{name}' inferred as '{actual}' does not satisfy bound '{expected_bound}'"
1294 )
1295 }
1296 IssueKind::ShadowedTemplateParam { name } => {
1297 format!(
1298 "Method template parameter '{name}' shadows class-level template parameter with the same name"
1299 )
1300 }
1301 IssueKind::FinalMethodOverridden {
1302 class,
1303 method,
1304 parent,
1305 } => {
1306 format!("Method {class}::{method}() cannot override final method from {parent}")
1307 }
1308 IssueKind::AbstractInstantiation { class } => {
1309 format!("Cannot instantiate abstract class {class}")
1310 }
1311 IssueKind::AbstractMethodCall { class, method } => {
1312 format!("Cannot call abstract method {class}::{method}()")
1313 }
1314 IssueKind::InterfaceInstantiation { class } => {
1315 format!("Cannot instantiate interface {class}")
1316 }
1317 IssueKind::InvalidOverride {
1318 class,
1319 method,
1320 detail,
1321 } => {
1322 format!("Method {class}::{method}() has #[Override] but {detail}")
1323 }
1324
1325 IssueKind::TaintedInput { sink } => format!("Tainted input reaching sink '{sink}'"),
1326 IssueKind::TaintedHtml => "Tainted HTML output — possible XSS".to_string(),
1327 IssueKind::TaintedSql => "Tainted SQL query — possible SQL injection".to_string(),
1328 IssueKind::TaintedShell => {
1329 "Tainted shell command — possible command injection".to_string()
1330 }
1331
1332 IssueKind::DeprecatedCall { name, message } => {
1333 let base = format!("Call to deprecated function {name}");
1334 append_deprecation_message(base, message)
1335 }
1336 IssueKind::DeprecatedProperty {
1337 class,
1338 property,
1339 message,
1340 } => {
1341 let base = format!("Property {class}::${property} is deprecated");
1342 append_deprecation_message(base, message)
1343 }
1344 IssueKind::DeprecatedConstant {
1345 class,
1346 constant,
1347 message,
1348 } => {
1349 let base = format!("Constant {class}::{constant} is deprecated");
1350 append_deprecation_message(base, message)
1351 }
1352 IssueKind::DeprecatedInterface { name, message } => {
1353 let base = format!("Interface {name} is deprecated");
1354 append_deprecation_message(base, message)
1355 }
1356 IssueKind::DeprecatedTrait { name, message } => {
1357 let base = format!("Trait {name} is deprecated");
1358 append_deprecation_message(base, message)
1359 }
1360 IssueKind::DeprecatedMethodCall {
1361 class,
1362 method,
1363 message,
1364 } => {
1365 let base = format!("Call to deprecated method {class}::{method}");
1366 append_deprecation_message(base, message)
1367 }
1368 IssueKind::DeprecatedMethod {
1369 class,
1370 method,
1371 message,
1372 } => {
1373 let base = format!("Method {class}::{method}() is deprecated");
1374 append_deprecation_message(base, message)
1375 }
1376 IssueKind::DeprecatedClass { name, message } => {
1377 let base = format!("Class {name} is deprecated");
1378 append_deprecation_message(base, message)
1379 }
1380 IssueKind::InternalMethod { class, method } => {
1381 format!("Method {class}::{method}() is marked @internal")
1382 }
1383 IssueKind::MissingReturnType { fn_name } => {
1384 format!("Function {fn_name}() has no return type annotation")
1385 }
1386 IssueKind::MissingParamType { fn_name, param } => {
1387 format!("Parameter ${param} of {fn_name}() has no type annotation")
1388 }
1389 IssueKind::MissingPropertyType { class, property } => {
1390 format!("Property {class}::${property} has no type annotation")
1391 }
1392 IssueKind::InvalidThrow { ty } => {
1393 format!("Thrown type '{ty}' does not extend Throwable")
1394 }
1395 IssueKind::InvalidCatch { ty } => {
1396 format!("Caught type '{ty}' does not extend Throwable")
1397 }
1398 IssueKind::MissingThrowsDocblock { class } => {
1399 format!("Exception {class} is thrown but not declared in @throws")
1400 }
1401 IssueKind::ImplicitToStringCast { class } => {
1402 format!("Class {class} is implicitly cast to string")
1403 }
1404 IssueKind::ImplicitFloatToIntCast { from } => {
1405 format!("Implicit cast from {from} to int truncates the fractional part")
1406 }
1407 IssueKind::ParseError { message } => format!("Parse error: {message}"),
1408 IssueKind::InvalidDocblock { message } => format!("Invalid docblock: {message}"),
1409 IssueKind::MixedArgument { param, fn_name } => {
1410 format!("Argument ${param} of {fn_name}() is mixed")
1411 }
1412 IssueKind::MixedAssignment { var } => {
1413 format!("Variable ${var} is assigned a mixed type")
1414 }
1415 IssueKind::MixedMethodCall { method } => {
1416 format!("Method {method}() called on mixed type")
1417 }
1418 IssueKind::MixedPropertyFetch { property } => {
1419 format!("Property ${property} fetched on mixed type")
1420 }
1421 IssueKind::MixedPropertyAssignment { property } => {
1422 format!("Property ${property} assigned on mixed type")
1423 }
1424 IssueKind::MixedArrayAccess => "Array access on mixed type".to_string(),
1425 IssueKind::MixedArrayOffset => "Mixed type used as array offset".to_string(),
1426 IssueKind::MixedClone => "cannot clone mixed".to_string(),
1427 IssueKind::InvalidClone { ty } => format!("cannot clone non-object {ty}"),
1428 IssueKind::PossiblyInvalidClone { ty } => {
1429 format!("cannot clone possibly non-object {ty}")
1430 }
1431 IssueKind::InvalidToString { class } => {
1432 format!("Method {class}::__toString() must return a string")
1433 }
1434 IssueKind::CircularInheritance { class } => {
1435 format!("Class {class} has a circular inheritance chain")
1436 }
1437 IssueKind::InvalidTraitUse { trait_name, reason } => {
1438 format!("Trait {trait_name} used incorrectly: {reason}")
1439 }
1440 IssueKind::WrongCaseFunction { used, canonical } => {
1441 format!("Function name '{used}' has incorrect casing; use '{canonical}'")
1442 }
1443 IssueKind::WrongCaseMethod {
1444 class,
1445 used,
1446 canonical,
1447 } => {
1448 format!("Method name '{class}::{used}' has incorrect casing; use '{canonical}'")
1449 }
1450 IssueKind::WrongCaseClass { used, canonical } => {
1451 format!("Class name '{used}' has incorrect casing; use '{canonical}'")
1452 }
1453 IssueKind::InvalidAttribute { message } => message.clone(),
1454 IssueKind::UndefinedAttributeClass { name } => {
1455 format!("Attribute class {name} does not exist")
1456 }
1457 IssueKind::ForbiddenCode { message } => message.clone(),
1458 IssueKind::DuplicateClass { name } => {
1459 format!("Class {name} has already been defined")
1460 }
1461 IssueKind::DuplicateInterface { name } => {
1462 format!("Interface {name} has already been defined")
1463 }
1464 IssueKind::DuplicateTrait { name } => {
1465 format!("Trait {name} has already been defined")
1466 }
1467 IssueKind::DuplicateEnum { name } => {
1468 format!("Enum {name} has already been defined")
1469 }
1470 IssueKind::DuplicateFunction { name } => {
1471 format!("Function {name}() has already been defined")
1472 }
1473 }
1474 }
1475}
1476
1477#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1482pub struct Issue {
1483 pub kind: IssueKind,
1484 pub severity: Severity,
1485 pub location: Location,
1486 pub snippet: Option<String>,
1487 pub suppressed: bool,
1488}
1489
1490impl Issue {
1491 pub fn new(kind: IssueKind, location: Location) -> Self {
1492 let severity = kind.default_severity();
1493 Self {
1494 severity,
1495 kind,
1496 location,
1497 snippet: None,
1498 suppressed: false,
1499 }
1500 }
1501
1502 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
1503 self.snippet = Some(snippet.into());
1504 self
1505 }
1506
1507 pub fn suppress(mut self) -> Self {
1508 self.suppressed = true;
1509 self
1510 }
1511}
1512
1513impl fmt::Display for Issue {
1514 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1515 let sev = match self.severity {
1516 Severity::Error => "error".red().to_string(),
1517 Severity::Warning => "warning".yellow().to_string(),
1518 Severity::Info => "info".blue().to_string(),
1519 };
1520 write!(
1521 f,
1522 "{} {}[{}] {}: {}",
1523 self.location.bright_black(),
1524 sev,
1525 self.kind.code().bright_black(),
1526 self.kind.name().bold(),
1527 self.kind.message()
1528 )
1529 }
1530}
1531
1532#[derive(Debug, Default)]
1537pub struct IssueBuffer {
1538 issues: Vec<Issue>,
1539 seen: HashSet<(&'static str, Arc<str>, u32, u16)>,
1540 file_suppressions: Vec<String>,
1542}
1543
1544impl IssueBuffer {
1545 pub fn new() -> Self {
1546 Self::default()
1547 }
1548
1549 pub fn add(&mut self, issue: Issue) {
1550 let key = (
1551 issue.kind.name(),
1552 issue.location.file.clone(),
1553 issue.location.line,
1554 issue.location.col_start,
1555 );
1556 if self.seen.insert(key) {
1557 self.issues.push(issue);
1558 }
1559 }
1560
1561 pub fn add_suppression(&mut self, name: impl Into<String>) {
1562 self.file_suppressions.push(name.into());
1563 }
1564
1565 pub fn into_issues(self) -> Vec<Issue> {
1567 self.issues
1568 .into_iter()
1569 .filter(|i| !i.suppressed)
1570 .filter(|i| !self.file_suppressions.contains(&i.kind.name().to_string()))
1571 .collect()
1572 }
1573
1574 pub fn suppress_range(&mut self, from: usize, suppressions: &[String]) {
1577 if suppressions.is_empty() {
1578 return;
1579 }
1580 for issue in self.issues[from..].iter_mut() {
1581 if suppressions.iter().any(|s| s == issue.kind.name()) {
1582 issue.suppressed = true;
1583 }
1584 }
1585 }
1586
1587 pub fn issue_count(&self) -> usize {
1590 self.issues.len()
1591 }
1592
1593 pub fn is_empty(&self) -> bool {
1594 self.issues.is_empty()
1595 }
1596
1597 pub fn len(&self) -> usize {
1598 self.issues.len()
1599 }
1600
1601 pub fn error_count(&self) -> usize {
1602 self.issues
1603 .iter()
1604 .filter(|i| !i.suppressed && i.severity == Severity::Error)
1605 .count()
1606 }
1607
1608 pub fn warning_count(&self) -> usize {
1609 self.issues
1610 .iter()
1611 .filter(|i| !i.suppressed && i.severity == Severity::Warning)
1612 .count()
1613 }
1614}
1615
1616#[cfg(test)]
1617mod code_tests {
1618 use super::*;
1619 use std::collections::HashSet;
1620
1621 fn one_of_each() -> Vec<IssueKind> {
1627 let s = || String::new();
1628 vec![
1629 IssueKind::InvalidScope { in_class: false },
1630 IssueKind::NonStaticSelfCall {
1631 class: s(),
1632 method: s(),
1633 },
1634 IssueKind::DirectConstructorCall { class: s() },
1635 IssueKind::UndefinedVariable { name: s() },
1636 IssueKind::UndefinedFunction { name: s() },
1637 IssueKind::UndefinedMethod {
1638 class: s(),
1639 method: s(),
1640 },
1641 IssueKind::UndefinedClass { name: s() },
1642 IssueKind::UndefinedProperty {
1643 class: s(),
1644 property: s(),
1645 },
1646 IssueKind::UndefinedConstant { name: s() },
1647 IssueKind::InaccessibleClassConstant {
1648 class: s(),
1649 constant: s(),
1650 },
1651 IssueKind::PossiblyUndefinedVariable { name: s() },
1652 IssueKind::UndefinedTrait { name: s() },
1653 IssueKind::ParentNotFound,
1654 IssueKind::NullArgument {
1655 param: s(),
1656 fn_name: s(),
1657 },
1658 IssueKind::NullPropertyFetch { property: s() },
1659 IssueKind::NullMethodCall { method: s() },
1660 IssueKind::NullArrayAccess,
1661 IssueKind::PossiblyNullArgument {
1662 param: s(),
1663 fn_name: s(),
1664 },
1665 IssueKind::PossiblyInvalidArgument {
1666 param: s(),
1667 fn_name: s(),
1668 expected: s(),
1669 actual: s(),
1670 },
1671 IssueKind::PossiblyNullPropertyFetch { property: s() },
1672 IssueKind::PossiblyNullMethodCall { method: s() },
1673 IssueKind::PossiblyNullArrayAccess,
1674 IssueKind::NullableReturnStatement {
1675 expected: s(),
1676 actual: s(),
1677 },
1678 IssueKind::InvalidReturnType {
1679 expected: s(),
1680 actual: s(),
1681 },
1682 IssueKind::InvalidArgument {
1683 param: s(),
1684 fn_name: s(),
1685 expected: s(),
1686 actual: s(),
1687 },
1688 IssueKind::TooFewArguments {
1689 fn_name: s(),
1690 expected: 0,
1691 actual: 0,
1692 },
1693 IssueKind::TooManyArguments {
1694 fn_name: s(),
1695 expected: 0,
1696 actual: 0,
1697 },
1698 IssueKind::InvalidNamedArgument {
1699 fn_name: s(),
1700 name: s(),
1701 },
1702 IssueKind::InvalidNamedArguments { fn_name: s() },
1703 IssueKind::InvalidPassByReference {
1704 fn_name: s(),
1705 param: s(),
1706 },
1707 IssueKind::InvalidPropertyFetch { ty: s() },
1708 IssueKind::InvalidArrayAccess { ty: s() },
1709 IssueKind::InvalidArrayAssignment { ty: s() },
1710 IssueKind::InvalidPropertyAssignment {
1711 property: s(),
1712 expected: s(),
1713 actual: s(),
1714 },
1715 IssueKind::InvalidCast { from: s(), to: s() },
1716 IssueKind::InvalidStaticInvocation {
1717 class: s(),
1718 method: s(),
1719 },
1720 IssueKind::InvalidOperand {
1721 op: s(),
1722 left: s(),
1723 right: s(),
1724 },
1725 IssueKind::PossiblyInvalidOperand {
1726 op: s(),
1727 left: s(),
1728 right: s(),
1729 },
1730 IssueKind::PossiblyNullOperand { op: s(), ty: s() },
1731 IssueKind::RawObjectIteration { ty: s() },
1732 IssueKind::PossiblyRawObjectIteration { ty: s() },
1733 IssueKind::MismatchingDocblockReturnType {
1734 declared: s(),
1735 inferred: s(),
1736 },
1737 IssueKind::MismatchingDocblockParamType {
1738 param: s(),
1739 declared: s(),
1740 inferred: s(),
1741 },
1742 IssueKind::TypeCheckMismatch {
1743 var: s(),
1744 expected: s(),
1745 actual: s(),
1746 },
1747 IssueKind::Trace {
1748 variable: s(),
1749 type_info: s(),
1750 },
1751 IssueKind::InvalidArrayOffset {
1752 expected: s(),
1753 actual: s(),
1754 },
1755 IssueKind::NonExistentArrayOffset { key: s() },
1756 IssueKind::PossiblyInvalidArrayOffset {
1757 expected: s(),
1758 actual: s(),
1759 },
1760 IssueKind::RedundantCondition { ty: s() },
1761 IssueKind::RedundantCast { from: s(), to: s() },
1762 IssueKind::UnnecessaryVarAnnotation { var: s() },
1763 IssueKind::TypeDoesNotContainType {
1764 left: s(),
1765 right: s(),
1766 },
1767 IssueKind::UnusedVariable { name: s() },
1768 IssueKind::UnusedParam { name: s() },
1769 IssueKind::UnreachableCode,
1770 IssueKind::UnhandledMatchCondition { detail: s() },
1771 IssueKind::UnusedMethod {
1772 class: s(),
1773 method: s(),
1774 },
1775 IssueKind::UnusedProperty {
1776 class: s(),
1777 property: s(),
1778 },
1779 IssueKind::UnusedFunction { name: s() },
1780 IssueKind::UnusedForeachValue { name: s() },
1781 IssueKind::ReadonlyPropertyAssignment {
1782 class: s(),
1783 property: s(),
1784 },
1785 IssueKind::UnimplementedAbstractMethod {
1786 class: s(),
1787 method: s(),
1788 },
1789 IssueKind::UnimplementedInterfaceMethod {
1790 class: s(),
1791 interface: s(),
1792 method: s(),
1793 },
1794 IssueKind::MethodSignatureMismatch {
1795 class: s(),
1796 method: s(),
1797 detail: s(),
1798 },
1799 IssueKind::OverriddenMethodAccess {
1800 class: s(),
1801 method: s(),
1802 },
1803 IssueKind::OverriddenPropertyAccess {
1804 class: s(),
1805 property: s(),
1806 },
1807 IssueKind::InvalidExtendClass {
1808 parent: s(),
1809 child: s(),
1810 },
1811 IssueKind::FinalMethodOverridden {
1812 class: s(),
1813 method: s(),
1814 parent: s(),
1815 },
1816 IssueKind::AbstractInstantiation { class: s() },
1817 IssueKind::AbstractMethodCall {
1818 class: s(),
1819 method: s(),
1820 },
1821 IssueKind::InterfaceInstantiation { class: s() },
1822 IssueKind::InvalidOverride {
1823 class: s(),
1824 method: s(),
1825 detail: s(),
1826 },
1827 IssueKind::CircularInheritance { class: s() },
1828 IssueKind::TaintedInput { sink: s() },
1829 IssueKind::TaintedHtml,
1830 IssueKind::TaintedSql,
1831 IssueKind::TaintedShell,
1832 IssueKind::InvalidTemplateParam {
1833 name: s(),
1834 expected_bound: s(),
1835 actual: s(),
1836 },
1837 IssueKind::ShadowedTemplateParam { name: s() },
1838 IssueKind::DeprecatedCall {
1839 name: s(),
1840 message: None,
1841 },
1842 IssueKind::DeprecatedProperty {
1843 class: s(),
1844 property: s(),
1845 message: None,
1846 },
1847 IssueKind::DeprecatedConstant {
1848 class: s(),
1849 constant: s(),
1850 message: None,
1851 },
1852 IssueKind::DeprecatedInterface {
1853 name: s(),
1854 message: None,
1855 },
1856 IssueKind::DeprecatedTrait {
1857 name: s(),
1858 message: None,
1859 },
1860 IssueKind::DeprecatedMethodCall {
1861 class: s(),
1862 method: s(),
1863 message: None,
1864 },
1865 IssueKind::DeprecatedMethod {
1866 class: s(),
1867 method: s(),
1868 message: None,
1869 },
1870 IssueKind::DeprecatedClass {
1871 name: s(),
1872 message: None,
1873 },
1874 IssueKind::InternalMethod {
1875 class: s(),
1876 method: s(),
1877 },
1878 IssueKind::MissingReturnType { fn_name: s() },
1879 IssueKind::MissingParamType {
1880 fn_name: s(),
1881 param: s(),
1882 },
1883 IssueKind::MissingPropertyType {
1884 class: s(),
1885 property: s(),
1886 },
1887 IssueKind::MissingThrowsDocblock { class: s() },
1888 IssueKind::InvalidDocblock { message: s() },
1889 IssueKind::MixedArgument {
1890 param: s(),
1891 fn_name: s(),
1892 },
1893 IssueKind::MixedAssignment { var: s() },
1894 IssueKind::MixedMethodCall { method: s() },
1895 IssueKind::MixedPropertyFetch { property: s() },
1896 IssueKind::MixedPropertyAssignment { property: s() },
1897 IssueKind::MixedArrayAccess,
1898 IssueKind::MixedArrayOffset,
1899 IssueKind::MixedClone,
1900 IssueKind::InvalidClone { ty: s() },
1901 IssueKind::PossiblyInvalidClone { ty: s() },
1902 IssueKind::InvalidToString { class: s() },
1903 IssueKind::InvalidTraitUse {
1904 trait_name: s(),
1905 reason: s(),
1906 },
1907 IssueKind::ParseError { message: s() },
1908 IssueKind::InvalidThrow { ty: s() },
1909 IssueKind::InvalidCatch { ty: s() },
1910 IssueKind::ImplicitToStringCast { class: s() },
1911 IssueKind::ImplicitFloatToIntCast { from: s() },
1912 IssueKind::WrongCaseFunction {
1913 used: s(),
1914 canonical: s(),
1915 },
1916 IssueKind::WrongCaseMethod {
1917 class: s(),
1918 used: s(),
1919 canonical: s(),
1920 },
1921 IssueKind::WrongCaseClass {
1922 used: s(),
1923 canonical: s(),
1924 },
1925 IssueKind::InvalidAttribute { message: s() },
1926 IssueKind::UndefinedAttributeClass { name: s() },
1927 IssueKind::ForbiddenCode { message: s() },
1928 IssueKind::DuplicateClass { name: s() },
1929 IssueKind::DuplicateInterface { name: s() },
1930 IssueKind::DuplicateTrait { name: s() },
1931 IssueKind::DuplicateEnum { name: s() },
1932 IssueKind::DuplicateFunction { name: s() },
1933 ]
1934 }
1935
1936 #[test]
1937 fn codes_have_expected_shape() {
1938 for kind in one_of_each() {
1939 let code = kind.code();
1940 assert!(
1941 code.len() == 7
1942 && code.starts_with("MIR")
1943 && code[3..].chars().all(|c| c.is_ascii_digit()),
1944 "code {code:?} for {} does not match MIR####",
1945 kind.name(),
1946 );
1947 }
1948 }
1949
1950 #[test]
1951 fn codes_are_unique() {
1952 let kinds = one_of_each();
1953 let mut seen: HashSet<&'static str> = HashSet::new();
1954 for kind in &kinds {
1955 assert!(
1956 seen.insert(kind.code()),
1957 "duplicate code {} (variant {})",
1958 kind.code(),
1959 kind.name(),
1960 );
1961 }
1962 }
1963
1964 #[test]
1965 fn display_includes_code() {
1966 let issue = Issue::new(
1967 IssueKind::UndefinedClass {
1968 name: "Foo".to_string(),
1969 },
1970 Location {
1971 file: Arc::from("src/x.php"),
1972 line: 1,
1973 line_end: 1,
1974 col_start: 0,
1975 col_end: 3,
1976 },
1977 );
1978 let raw = format!("{issue}");
1981 let stripped: String = {
1982 let mut out = String::new();
1983 let mut chars = raw.chars();
1984 while let Some(c) = chars.next() {
1985 if c == '\u{1b}' {
1986 for c2 in chars.by_ref() {
1987 if c2 == 'm' {
1988 break;
1989 }
1990 }
1991 } else {
1992 out.push(c);
1993 }
1994 }
1995 out
1996 };
1997 assert!(
1998 stripped.contains("error[MIR0005] UndefinedClass:"),
1999 "Display output missing code/name segment: {stripped:?}",
2000 );
2001 }
2002
2003 #[test]
2004 fn default_severity_for_code_round_trips() {
2005 for kind in one_of_each() {
2006 let code = kind.code();
2007 assert_eq!(
2008 IssueKind::default_severity_for_code(code),
2009 Some(kind.default_severity()),
2010 "severity mismatch for {code} (variant {})",
2011 kind.name(),
2012 );
2013 }
2014 }
2015
2016 #[test]
2017 fn default_severity_for_code_unknown_returns_none() {
2018 assert_eq!(IssueKind::default_severity_for_code("MIR9999"), None);
2019 assert_eq!(IssueKind::default_severity_for_code(""), None);
2020 assert_eq!(IssueKind::default_severity_for_code("mir0001"), None);
2021 }
2022
2023 #[test]
2026 fn one_of_each_has_every_variant() {
2027 assert_eq!(one_of_each().len(), 121);
2030 }
2031}