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 InvalidPassByReference { fn_name: String, param: String },
157 InvalidPropertyFetch { ty: String },
160 InvalidArrayAccess { ty: String },
163 InvalidArrayAssignment { ty: String },
166 InvalidPropertyAssignment {
169 property: String,
170 expected: String,
171 actual: String,
172 },
173 InvalidCast { from: String, to: String },
176 InvalidStaticInvocation { class: String, method: String },
179 InvalidOperand {
183 op: String,
184 left: String,
185 right: String,
186 },
187 PossiblyInvalidOperand {
190 op: String,
191 left: String,
192 right: String,
193 },
194 PossiblyNullOperand { op: String, ty: String },
197 MismatchingDocblockReturnType { declared: String, inferred: String },
199 MismatchingDocblockParamType {
201 param: String,
202 declared: String,
203 inferred: String,
204 },
205 TypeCheckMismatch {
208 var: String,
209 expected: String,
210 actual: String,
211 },
212
213 InvalidArrayOffset { expected: String, actual: String },
216 NonExistentArrayOffset { key: String },
218 PossiblyInvalidArrayOffset { expected: String, actual: String },
221
222 RedundantCondition { ty: String },
226 RedundantCast { from: String, to: String },
229 UnnecessaryVarAnnotation { var: String },
231 TypeDoesNotContainType { left: String, right: String },
233 ParadoxicalCondition { value: String },
236
237 UnusedVariable { name: String },
241 UnusedParam { name: String },
244 UnreachableCode,
247 UnhandledMatchCondition { detail: String },
250 UnusedMethod { class: String, method: String },
253 UnusedProperty { class: String, property: String },
256 UnusedFunction { name: String },
259 UnusedForeachValue { name: String },
262
263 ReadonlyPropertyAssignment { class: String, property: String },
267
268 UnimplementedAbstractMethod { class: String, method: String },
272 UnimplementedInterfaceMethod {
275 class: String,
276 interface: String,
277 method: String,
278 },
279 MethodSignatureMismatch {
282 class: String,
283 method: String,
284 detail: String,
285 },
286 OverriddenMethodAccess { class: String, method: String },
289 OverriddenPropertyAccess { class: String, property: String },
292 DirectConstructorCall { class: String },
295 InvalidExtendClass { parent: String, child: String },
298 FinalMethodOverridden {
301 class: String,
302 method: String,
303 parent: String,
304 },
305 AbstractInstantiation { class: String },
308 AbstractMethodCall { class: String, method: String },
311 InterfaceInstantiation { class: String },
314 InvalidOverride {
318 class: String,
319 method: String,
320 detail: String,
321 },
322
323 TaintedInput { sink: String },
327 TaintedHtml,
330 TaintedSql,
333 TaintedShell,
336
337 InvalidTemplateParam {
341 name: String,
342 expected_bound: String,
343 actual: String,
344 },
345 ShadowedTemplateParam { name: String },
348
349 DeprecatedCall {
353 name: String,
354 message: Option<Arc<str>>,
355 },
356 DeprecatedProperty {
359 class: String,
360 property: String,
361 message: Option<Arc<str>>,
362 },
363 DeprecatedConstant {
366 class: String,
367 constant: String,
368 message: Option<Arc<str>>,
369 },
370 DeprecatedInterface {
373 name: String,
374 message: Option<Arc<str>>,
375 },
376 DeprecatedTrait {
379 name: String,
380 message: Option<Arc<str>>,
381 },
382 DeprecatedMethodCall {
385 class: String,
386 method: String,
387 message: Option<Arc<str>>,
388 },
389 DeprecatedMethod {
392 class: String,
393 method: String,
394 message: Option<Arc<str>>,
395 },
396 DeprecatedClass {
399 name: String,
400 message: Option<Arc<str>>,
401 },
402 InternalMethod { class: String, method: String },
405 MissingReturnType { fn_name: String },
407 MissingParamType { fn_name: String, param: String },
409 InvalidThrow { ty: String },
412 InvalidCatch { ty: String },
415 MissingThrowsDocblock { class: String },
418 ImplicitToStringCast { class: String },
421 ImplicitFloatToIntCast { from: String },
424 ParseError { message: String },
427 InvalidDocblock { message: String },
430 MixedArgument { param: String, fn_name: String },
432 MixedAssignment { var: String },
434 MixedMethodCall { method: String },
437 MixedPropertyFetch { property: String },
439 MixedClone,
442 InvalidClone { ty: String },
446 PossiblyInvalidClone { ty: String },
450 InvalidToString { class: String },
454 CircularInheritance { class: String },
457
458 InvalidTraitUse { trait_name: String, reason: String },
462
463 InvalidAttribute { message: String },
467 UndefinedAttributeClass { name: String },
470
471 WrongCaseFunction { used: String, canonical: String },
475 WrongCaseMethod {
478 class: String,
479 used: String,
480 canonical: String,
481 },
482 WrongCaseClass { used: String, canonical: String },
485 DuplicateClass { name: String },
488}
489
490fn append_deprecation_message(base: String, message: &Option<Arc<str>>) -> String {
491 match message.as_deref().filter(|m| !m.is_empty()) {
492 Some(msg) => format!("{base}: {msg}"),
493 None => base,
494 }
495}
496
497impl IssueKind {
498 pub fn default_severity(&self) -> Severity {
500 match self {
501 IssueKind::NonStaticSelfCall { .. }
503 | IssueKind::DirectConstructorCall { .. }
504 | IssueKind::InvalidScope { .. }
505 | IssueKind::UndefinedVariable { .. }
506 | IssueKind::UndefinedFunction { .. }
507 | IssueKind::UndefinedMethod { .. }
508 | IssueKind::UndefinedClass { .. }
509 | IssueKind::UndefinedConstant { .. }
510 | IssueKind::InaccessibleClassConstant { .. }
511 | IssueKind::InvalidReturnType { .. }
512 | IssueKind::InvalidArgument { .. }
513 | IssueKind::TooFewArguments { .. }
514 | IssueKind::TooManyArguments { .. }
515 | IssueKind::InvalidNamedArgument { .. }
516 | IssueKind::InvalidPassByReference { .. }
517 | IssueKind::InvalidThrow { .. }
518 | IssueKind::InvalidCatch { .. }
519 | IssueKind::InvalidStaticInvocation { .. }
520 | IssueKind::UnimplementedAbstractMethod { .. }
521 | IssueKind::UnimplementedInterfaceMethod { .. }
522 | IssueKind::MethodSignatureMismatch { .. }
523 | IssueKind::InvalidExtendClass { .. }
524 | IssueKind::FinalMethodOverridden { .. }
525 | IssueKind::AbstractInstantiation { .. }
526 | IssueKind::AbstractMethodCall { .. }
527 | IssueKind::InterfaceInstantiation { .. }
528 | IssueKind::InvalidOverride { .. }
529 | IssueKind::InvalidTemplateParam { .. }
530 | IssueKind::ReadonlyPropertyAssignment { .. }
531 | IssueKind::ParseError { .. }
532 | IssueKind::TaintedInput { .. }
533 | IssueKind::TaintedHtml
534 | IssueKind::TaintedSql
535 | IssueKind::TaintedShell
536 | IssueKind::CircularInheritance { .. }
537 | IssueKind::InvalidTraitUse { .. }
538 | IssueKind::UndefinedTrait { .. }
539 | IssueKind::InvalidClone { .. }
540 | IssueKind::InvalidToString { .. }
541 | IssueKind::TypeCheckMismatch { .. }
542 | IssueKind::ParentNotFound => Severity::Error,
543
544 IssueKind::NullArgument { .. }
546 | IssueKind::NullPropertyFetch { .. }
547 | IssueKind::NullMethodCall { .. }
548 | IssueKind::NullArrayAccess
549 | IssueKind::NullableReturnStatement { .. }
550 | IssueKind::InvalidPropertyFetch { .. }
551 | IssueKind::InvalidArrayAccess { .. }
552 | IssueKind::InvalidArrayAssignment { .. }
553 | IssueKind::InvalidPropertyAssignment { .. }
554 | IssueKind::InvalidArrayOffset { .. }
555 | IssueKind::NonExistentArrayOffset { .. }
556 | IssueKind::PossiblyInvalidArrayOffset { .. }
557 | IssueKind::UndefinedProperty { .. }
558 | IssueKind::InvalidOperand { .. }
559 | IssueKind::OverriddenMethodAccess { .. }
560 | IssueKind::OverriddenPropertyAccess { .. }
561 | IssueKind::ImplicitToStringCast { .. }
562 | IssueKind::ImplicitFloatToIntCast { .. }
563 | IssueKind::UnusedVariable { .. }
564 | IssueKind::UnusedForeachValue { .. }
565 | IssueKind::ParadoxicalCondition { .. }
566 | IssueKind::UnhandledMatchCondition { .. }
567 | IssueKind::InvalidStringClass { .. } => Severity::Warning,
568
569 IssueKind::PossiblyUndefinedVariable { .. } => Severity::Warning,
571
572 IssueKind::PossiblyNullArgument { .. }
574 | IssueKind::PossiblyInvalidArgument { .. }
575 | IssueKind::PossiblyNullPropertyFetch { .. }
576 | IssueKind::PossiblyNullMethodCall { .. }
577 | IssueKind::PossiblyNullArrayAccess
578 | IssueKind::PossiblyInvalidClone { .. }
579 | IssueKind::PossiblyInvalidOperand { .. }
580 | IssueKind::PossiblyNullOperand { .. } => Severity::Info,
581
582 IssueKind::RedundantCondition { .. }
584 | IssueKind::RedundantCast { .. }
585 | IssueKind::UnnecessaryVarAnnotation { .. }
586 | IssueKind::TypeDoesNotContainType { .. }
587 | IssueKind::UnusedParam { .. }
588 | IssueKind::UnreachableCode
589 | IssueKind::UnusedMethod { .. }
590 | IssueKind::UnusedProperty { .. }
591 | IssueKind::UnusedFunction { .. }
592 | IssueKind::DeprecatedCall { .. }
593 | IssueKind::DeprecatedProperty { .. }
594 | IssueKind::DeprecatedConstant { .. }
595 | IssueKind::DeprecatedInterface { .. }
596 | IssueKind::DeprecatedTrait { .. }
597 | IssueKind::DeprecatedMethodCall { .. }
598 | IssueKind::DeprecatedMethod { .. }
599 | IssueKind::DeprecatedClass { .. }
600 | IssueKind::InternalMethod { .. }
601 | IssueKind::MissingReturnType { .. }
602 | IssueKind::MissingParamType { .. }
603 | IssueKind::MismatchingDocblockReturnType { .. }
604 | IssueKind::MismatchingDocblockParamType { .. }
605 | IssueKind::InvalidDocblock { .. }
606 | IssueKind::InvalidCast { .. }
607 | IssueKind::MixedArgument { .. }
608 | IssueKind::MixedAssignment { .. }
609 | IssueKind::MixedMethodCall { .. }
610 | IssueKind::MixedPropertyFetch { .. }
611 | IssueKind::MixedClone
612 | IssueKind::ShadowedTemplateParam { .. }
613 | IssueKind::MissingThrowsDocblock { .. }
614 | IssueKind::WrongCaseFunction { .. }
615 | IssueKind::WrongCaseMethod { .. }
616 | IssueKind::WrongCaseClass { .. }
617 | IssueKind::InvalidAttribute { .. }
618 | IssueKind::UndefinedAttributeClass { .. } => Severity::Info,
619 IssueKind::DuplicateClass { .. } => Severity::Error,
620 }
621 }
622
623 pub fn code(&self) -> &'static str {
651 match self {
652 IssueKind::NonStaticSelfCall { .. } => "MIR0216",
654 IssueKind::DirectConstructorCall { .. } => "MIR0217",
655 IssueKind::InvalidScope { .. } => "MIR0001",
656 IssueKind::UndefinedVariable { .. } => "MIR0002",
657 IssueKind::UndefinedFunction { .. } => "MIR0003",
658 IssueKind::UndefinedMethod { .. } => "MIR0004",
659 IssueKind::UndefinedClass { .. } => "MIR0005",
660 IssueKind::UndefinedProperty { .. } => "MIR0006",
661 IssueKind::UndefinedConstant { .. } => "MIR0007",
662 IssueKind::InaccessibleClassConstant { .. } => "MIR0011",
663 IssueKind::PossiblyUndefinedVariable { .. } => "MIR0008",
664 IssueKind::UndefinedTrait { .. } => "MIR0009",
665 IssueKind::ParentNotFound => "MIR0010",
666
667 IssueKind::NullArgument { .. } => "MIR0100",
669 IssueKind::NullPropertyFetch { .. } => "MIR0101",
670 IssueKind::NullMethodCall { .. } => "MIR0102",
671 IssueKind::NullArrayAccess => "MIR0103",
672 IssueKind::PossiblyNullArgument { .. } => "MIR0104",
673 IssueKind::PossiblyInvalidArgument { .. } => "MIR0105",
674 IssueKind::PossiblyNullPropertyFetch { .. } => "MIR0106",
675 IssueKind::PossiblyNullMethodCall { .. } => "MIR0107",
676 IssueKind::PossiblyNullArrayAccess => "MIR0108",
677 IssueKind::NullableReturnStatement { .. } => "MIR0109",
678
679 IssueKind::InvalidReturnType { .. } => "MIR0200",
681 IssueKind::InvalidArgument { .. } => "MIR0201",
682 IssueKind::TooFewArguments { .. } => "MIR0202",
683 IssueKind::TooManyArguments { .. } => "MIR0203",
684 IssueKind::InvalidNamedArgument { .. } => "MIR0204",
685 IssueKind::InvalidPassByReference { .. } => "MIR0205",
686 IssueKind::InvalidPropertyFetch { .. } => "MIR0218",
687 IssueKind::InvalidArrayAccess { .. } => "MIR0219",
688 IssueKind::InvalidArrayAssignment { .. } => "MIR0220",
689 IssueKind::InvalidPropertyAssignment { .. } => "MIR0206",
690 IssueKind::InvalidCast { .. } => "MIR0207",
691 IssueKind::InvalidStaticInvocation { .. } => "MIR0215",
692 IssueKind::InvalidOperand { .. } => "MIR0208",
693 IssueKind::PossiblyInvalidOperand { .. } => "MIR0213",
694 IssueKind::PossiblyNullOperand { .. } => "MIR0214",
695 IssueKind::MismatchingDocblockReturnType { .. } => "MIR0209",
696 IssueKind::MismatchingDocblockParamType { .. } => "MIR0210",
697 IssueKind::InvalidStringClass { .. } => "MIR0211",
698 IssueKind::TypeCheckMismatch { .. } => "MIR0212",
699
700 IssueKind::InvalidArrayOffset { .. } => "MIR0300",
702 IssueKind::NonExistentArrayOffset { .. } => "MIR0301",
703 IssueKind::PossiblyInvalidArrayOffset { .. } => "MIR0302",
704
705 IssueKind::RedundantCondition { .. } => "MIR0400",
707 IssueKind::RedundantCast { .. } => "MIR0401",
708 IssueKind::UnnecessaryVarAnnotation { .. } => "MIR0402",
709 IssueKind::TypeDoesNotContainType { .. } => "MIR0403",
710 IssueKind::ParadoxicalCondition { .. } => "MIR0404",
711 IssueKind::UnhandledMatchCondition { .. } => "MIR0405",
712
713 IssueKind::UnusedVariable { .. } => "MIR0500",
715 IssueKind::UnusedParam { .. } => "MIR0501",
716 IssueKind::UnreachableCode => "MIR0502",
717 IssueKind::UnusedMethod { .. } => "MIR0503",
718 IssueKind::UnusedProperty { .. } => "MIR0504",
719 IssueKind::UnusedFunction { .. } => "MIR0505",
720 IssueKind::UnusedForeachValue { .. } => "MIR0506",
721
722 IssueKind::ReadonlyPropertyAssignment { .. } => "MIR0600",
724
725 IssueKind::UnimplementedAbstractMethod { .. } => "MIR0700",
727 IssueKind::UnimplementedInterfaceMethod { .. } => "MIR0701",
728 IssueKind::MethodSignatureMismatch { .. } => "MIR0702",
729 IssueKind::OverriddenMethodAccess { .. } => "MIR0703",
730 IssueKind::OverriddenPropertyAccess { .. } => "MIR0710",
731 IssueKind::InvalidExtendClass { .. } => "MIR0704",
732 IssueKind::FinalMethodOverridden { .. } => "MIR0705",
733 IssueKind::AbstractInstantiation { .. } => "MIR0706",
734 IssueKind::AbstractMethodCall { .. } => "MIR0711",
735 IssueKind::InterfaceInstantiation { .. } => "MIR0709",
736 IssueKind::CircularInheritance { .. } => "MIR0707",
737 IssueKind::InvalidOverride { .. } => "MIR0708",
738
739 IssueKind::TaintedInput { .. } => "MIR0800",
741 IssueKind::TaintedHtml => "MIR0801",
742 IssueKind::TaintedSql => "MIR0802",
743 IssueKind::TaintedShell => "MIR0803",
744
745 IssueKind::InvalidTemplateParam { .. } => "MIR0900",
747 IssueKind::ShadowedTemplateParam { .. } => "MIR0901",
748
749 IssueKind::DeprecatedCall { .. } => "MIR1000",
751 IssueKind::WrongCaseFunction { .. } => "MIR1009",
752 IssueKind::WrongCaseMethod { .. } => "MIR1010",
753 IssueKind::WrongCaseClass { .. } => "MIR1011",
754 IssueKind::DeprecatedProperty { .. } => "MIR1005",
755 IssueKind::DeprecatedInterface { .. } => "MIR1006",
756 IssueKind::DeprecatedTrait { .. } => "MIR1007",
757 IssueKind::DeprecatedConstant { .. } => "MIR1008",
758 IssueKind::DeprecatedMethodCall { .. } => "MIR1001",
759 IssueKind::DeprecatedMethod { .. } => "MIR1002",
760 IssueKind::DeprecatedClass { .. } => "MIR1003",
761 IssueKind::InternalMethod { .. } => "MIR1004",
762
763 IssueKind::MissingReturnType { .. } => "MIR1100",
765 IssueKind::MissingParamType { .. } => "MIR1101",
766 IssueKind::MissingThrowsDocblock { .. } => "MIR1102",
767 IssueKind::InvalidDocblock { .. } => "MIR1103",
768
769 IssueKind::MixedArgument { .. } => "MIR1200",
771 IssueKind::MixedAssignment { .. } => "MIR1201",
772 IssueKind::MixedMethodCall { .. } => "MIR1202",
773 IssueKind::MixedPropertyFetch { .. } => "MIR1203",
774 IssueKind::MixedClone => "MIR1204",
775 IssueKind::InvalidClone { .. } => "MIR1205",
776 IssueKind::PossiblyInvalidClone { .. } => "MIR1206",
777 IssueKind::InvalidToString { .. } => "MIR1207",
778
779 IssueKind::InvalidTraitUse { .. } => "MIR1300",
781
782 IssueKind::ParseError { .. } => "MIR1400",
784
785 IssueKind::InvalidAttribute { .. } => "MIR1600",
787 IssueKind::UndefinedAttributeClass { .. } => "MIR1601",
788 IssueKind::DuplicateClass { .. } => "MIR1602",
789
790 IssueKind::InvalidThrow { .. } => "MIR1500",
792 IssueKind::InvalidCatch { .. } => "MIR1503",
793 IssueKind::ImplicitToStringCast { .. } => "MIR1501",
794 IssueKind::ImplicitFloatToIntCast { .. } => "MIR1502",
795 }
796 }
797
798 pub fn name(&self) -> &'static str {
800 match self {
801 IssueKind::NonStaticSelfCall { .. } => "NonStaticSelfCall",
802 IssueKind::DirectConstructorCall { .. } => "DirectConstructorCall",
803 IssueKind::InvalidScope { .. } => "InvalidScope",
804 IssueKind::UndefinedVariable { .. } => "UndefinedVariable",
805 IssueKind::UndefinedFunction { .. } => "UndefinedFunction",
806 IssueKind::UndefinedMethod { .. } => "UndefinedMethod",
807 IssueKind::UndefinedClass { .. } => "UndefinedClass",
808 IssueKind::UndefinedProperty { .. } => "UndefinedProperty",
809 IssueKind::UndefinedConstant { .. } => "UndefinedConstant",
810 IssueKind::InaccessibleClassConstant { .. } => "InaccessibleClassConstant",
811 IssueKind::PossiblyUndefinedVariable { .. } => "PossiblyUndefinedVariable",
812 IssueKind::UndefinedTrait { .. } => "UndefinedTrait",
813 IssueKind::ParentNotFound => "ParentNotFound",
814 IssueKind::InvalidStringClass { .. } => "InvalidStringClass",
815 IssueKind::NullArgument { .. } => "NullArgument",
816 IssueKind::NullPropertyFetch { .. } => "NullPropertyFetch",
817 IssueKind::NullMethodCall { .. } => "NullMethodCall",
818 IssueKind::NullArrayAccess => "NullArrayAccess",
819 IssueKind::PossiblyNullArgument { .. } => "PossiblyNullArgument",
820 IssueKind::PossiblyInvalidArgument { .. } => "PossiblyInvalidArgument",
821 IssueKind::PossiblyNullPropertyFetch { .. } => "PossiblyNullPropertyFetch",
822 IssueKind::PossiblyNullMethodCall { .. } => "PossiblyNullMethodCall",
823 IssueKind::PossiblyNullArrayAccess => "PossiblyNullArrayAccess",
824 IssueKind::NullableReturnStatement { .. } => "NullableReturnStatement",
825 IssueKind::InvalidReturnType { .. } => "InvalidReturnType",
826 IssueKind::InvalidArgument { .. } => "InvalidArgument",
827 IssueKind::TooFewArguments { .. } => "TooFewArguments",
828 IssueKind::TooManyArguments { .. } => "TooManyArguments",
829 IssueKind::InvalidNamedArgument { .. } => "InvalidNamedArgument",
830 IssueKind::InvalidPassByReference { .. } => "InvalidPassByReference",
831 IssueKind::InvalidPropertyFetch { .. } => "InvalidPropertyFetch",
832 IssueKind::InvalidArrayAccess { .. } => "InvalidArrayAccess",
833 IssueKind::InvalidArrayAssignment { .. } => "InvalidArrayAssignment",
834 IssueKind::InvalidPropertyAssignment { .. } => "InvalidPropertyAssignment",
835 IssueKind::InvalidCast { .. } => "InvalidCast",
836 IssueKind::InvalidStaticInvocation { .. } => "InvalidStaticInvocation",
837 IssueKind::InvalidOperand { .. } => "InvalidOperand",
838 IssueKind::PossiblyInvalidOperand { .. } => "PossiblyInvalidOperand",
839 IssueKind::PossiblyNullOperand { .. } => "PossiblyNullOperand",
840 IssueKind::MismatchingDocblockReturnType { .. } => "MismatchingDocblockReturnType",
841 IssueKind::MismatchingDocblockParamType { .. } => "MismatchingDocblockParamType",
842 IssueKind::TypeCheckMismatch { .. } => "TypeCheckMismatch",
843 IssueKind::InvalidArrayOffset { .. } => "InvalidArrayOffset",
844 IssueKind::NonExistentArrayOffset { .. } => "NonExistentArrayOffset",
845 IssueKind::PossiblyInvalidArrayOffset { .. } => "PossiblyInvalidArrayOffset",
846 IssueKind::RedundantCondition { .. } => "RedundantCondition",
847 IssueKind::RedundantCast { .. } => "RedundantCast",
848 IssueKind::UnnecessaryVarAnnotation { .. } => "UnnecessaryVarAnnotation",
849 IssueKind::TypeDoesNotContainType { .. } => "TypeDoesNotContainType",
850 IssueKind::ParadoxicalCondition { .. } => "ParadoxicalCondition",
851 IssueKind::UnhandledMatchCondition { .. } => "UnhandledMatchCondition",
852 IssueKind::UnusedVariable { .. } => "UnusedVariable",
853 IssueKind::UnusedParam { .. } => "UnusedParam",
854 IssueKind::UnreachableCode => "UnreachableCode",
855 IssueKind::UnusedMethod { .. } => "UnusedMethod",
856 IssueKind::UnusedProperty { .. } => "UnusedProperty",
857 IssueKind::UnusedFunction { .. } => "UnusedFunction",
858 IssueKind::UnusedForeachValue { .. } => "UnusedForeachValue",
859 IssueKind::UnimplementedAbstractMethod { .. } => "UnimplementedAbstractMethod",
860 IssueKind::UnimplementedInterfaceMethod { .. } => "UnimplementedInterfaceMethod",
861 IssueKind::MethodSignatureMismatch { .. } => "MethodSignatureMismatch",
862 IssueKind::OverriddenMethodAccess { .. } => "OverriddenMethodAccess",
863 IssueKind::OverriddenPropertyAccess { .. } => "OverriddenPropertyAccess",
864 IssueKind::InvalidExtendClass { .. } => "InvalidExtendClass",
865 IssueKind::FinalMethodOverridden { .. } => "FinalMethodOverridden",
866 IssueKind::AbstractInstantiation { .. } => "AbstractInstantiation",
867 IssueKind::AbstractMethodCall { .. } => "AbstractMethodCall",
868 IssueKind::InterfaceInstantiation { .. } => "InterfaceInstantiation",
869 IssueKind::InvalidOverride { .. } => "InvalidOverride",
870 IssueKind::ReadonlyPropertyAssignment { .. } => "ReadonlyPropertyAssignment",
871 IssueKind::InvalidTemplateParam { .. } => "InvalidTemplateParam",
872 IssueKind::ShadowedTemplateParam { .. } => "ShadowedTemplateParam",
873 IssueKind::TaintedInput { .. } => "TaintedInput",
874 IssueKind::TaintedHtml => "TaintedHtml",
875 IssueKind::TaintedSql => "TaintedSql",
876 IssueKind::TaintedShell => "TaintedShell",
877 IssueKind::DeprecatedCall { .. } => "DeprecatedCall",
878 IssueKind::DeprecatedProperty { .. } => "DeprecatedProperty",
879 IssueKind::DeprecatedConstant { .. } => "DeprecatedConstant",
880 IssueKind::DeprecatedInterface { .. } => "DeprecatedInterface",
881 IssueKind::DeprecatedTrait { .. } => "DeprecatedTrait",
882 IssueKind::DeprecatedMethodCall { .. } => "DeprecatedMethodCall",
883 IssueKind::DeprecatedMethod { .. } => "DeprecatedMethod",
884 IssueKind::DeprecatedClass { .. } => "DeprecatedClass",
885 IssueKind::InternalMethod { .. } => "InternalMethod",
886 IssueKind::MissingReturnType { .. } => "MissingReturnType",
887 IssueKind::MissingParamType { .. } => "MissingParamType",
888 IssueKind::InvalidThrow { .. } => "InvalidThrow",
889 IssueKind::InvalidCatch { .. } => "InvalidCatch",
890 IssueKind::MissingThrowsDocblock { .. } => "MissingThrowsDocblock",
891 IssueKind::ImplicitToStringCast { .. } => "ImplicitToStringCast",
892 IssueKind::ImplicitFloatToIntCast { .. } => "ImplicitFloatToIntCast",
893 IssueKind::ParseError { .. } => "ParseError",
894 IssueKind::InvalidDocblock { .. } => "InvalidDocblock",
895 IssueKind::MixedArgument { .. } => "MixedArgument",
896 IssueKind::MixedAssignment { .. } => "MixedAssignment",
897 IssueKind::MixedMethodCall { .. } => "MixedMethodCall",
898 IssueKind::MixedPropertyFetch { .. } => "MixedPropertyFetch",
899 IssueKind::MixedClone => "MixedClone",
900 IssueKind::InvalidClone { .. } => "InvalidClone",
901 IssueKind::PossiblyInvalidClone { .. } => "PossiblyInvalidClone",
902 IssueKind::InvalidToString { .. } => "InvalidToString",
903 IssueKind::CircularInheritance { .. } => "CircularInheritance",
904 IssueKind::InvalidTraitUse { .. } => "InvalidTraitUse",
905 IssueKind::WrongCaseFunction { .. } => "WrongCaseFunction",
906 IssueKind::WrongCaseMethod { .. } => "WrongCaseMethod",
907 IssueKind::WrongCaseClass { .. } => "WrongCaseClass",
908 IssueKind::InvalidAttribute { .. } => "InvalidAttribute",
909 IssueKind::UndefinedAttributeClass { .. } => "UndefinedAttributeClass",
910 IssueKind::DuplicateClass { .. } => "DuplicateClass",
911 }
912 }
913
914 pub fn message(&self) -> String {
916 match self {
917 IssueKind::NonStaticSelfCall { class, method } => {
918 format!("Non-static method {class}::{method}() cannot be called statically")
919 }
920 IssueKind::DirectConstructorCall { class } => {
921 format!("Cannot call constructor of {class} directly")
922 }
923 IssueKind::InvalidScope { in_class } => {
924 if *in_class {
925 "$this cannot be used in a static method".to_string()
926 } else {
927 "$this cannot be used outside of a class".to_string()
928 }
929 }
930 IssueKind::UndefinedVariable { name } => format!("Variable ${name} is not defined"),
931 IssueKind::UndefinedFunction { name } => format!("Function {name}() is not defined"),
932 IssueKind::UndefinedMethod { class, method } => {
933 format!("Method {class}::{method}() does not exist")
934 }
935 IssueKind::UndefinedClass { name } => format!("Class {name} does not exist"),
936 IssueKind::UndefinedProperty { class, property } => {
937 format!("Property {class}::${property} does not exist")
938 }
939 IssueKind::UndefinedConstant { name } => format!("Constant {name} is not defined"),
940 IssueKind::InaccessibleClassConstant { class, constant } => {
941 format!("Cannot access constant {class}::{constant}")
942 }
943 IssueKind::PossiblyUndefinedVariable { name } => {
944 format!("Variable ${name} might not be defined")
945 }
946 IssueKind::UndefinedTrait { name } => format!("Trait {name} does not exist"),
947 IssueKind::ParentNotFound => {
948 "Cannot use parent:: when current class has no parent".to_string()
949 }
950 IssueKind::InvalidStringClass { actual } => {
951 format!("Dynamic class instantiation requires string or class-string type, got '{actual}'")
952 }
953
954 IssueKind::NullArgument { param, fn_name } => {
955 format!("Argument ${param} of {fn_name}() cannot be null")
956 }
957 IssueKind::NullPropertyFetch { property } => {
958 format!("Cannot access property ${property} on null")
959 }
960 IssueKind::NullMethodCall { method } => {
961 format!("Cannot call method {method}() on null")
962 }
963 IssueKind::NullArrayAccess => "Cannot access array on null".to_string(),
964 IssueKind::PossiblyNullArgument { param, fn_name } => {
965 format!("Argument ${param} of {fn_name}() might be null")
966 }
967 IssueKind::PossiblyInvalidArgument {
968 param,
969 fn_name,
970 expected,
971 actual,
972 } => {
973 format!("Argument ${param} of {fn_name}() expects '{expected}', possibly different type '{actual}' provided")
974 }
975 IssueKind::PossiblyNullPropertyFetch { property } => {
976 format!("Cannot access property ${property} on possibly null value")
977 }
978 IssueKind::PossiblyNullMethodCall { method } => {
979 format!("Cannot call method {method}() on possibly null value")
980 }
981 IssueKind::PossiblyNullArrayAccess => {
982 "Cannot access array on possibly null value".to_string()
983 }
984 IssueKind::NullableReturnStatement { expected, actual } => {
985 format!("Return type '{actual}' is not compatible with declared '{expected}'")
986 }
987
988 IssueKind::InvalidReturnType { expected, actual } => {
989 format!("Return type '{actual}' is not compatible with declared '{expected}'")
990 }
991 IssueKind::InvalidArgument {
992 param,
993 fn_name,
994 expected,
995 actual,
996 } => {
997 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}'")
998 }
999 IssueKind::TooFewArguments {
1000 fn_name,
1001 expected,
1002 actual,
1003 } => {
1004 format!(
1005 "Too few arguments for {}(): expected {}, got {}",
1006 fn_name, expected, actual
1007 )
1008 }
1009 IssueKind::TooManyArguments {
1010 fn_name,
1011 expected,
1012 actual,
1013 } => {
1014 format!(
1015 "Too many arguments for {}(): expected {}, got {}",
1016 fn_name, expected, actual
1017 )
1018 }
1019 IssueKind::InvalidNamedArgument { fn_name, name } => {
1020 format!("{}() has no parameter named ${}", fn_name, name)
1021 }
1022 IssueKind::InvalidPassByReference { fn_name, param } => {
1023 format!(
1024 "Argument ${} of {}() must be passed by reference",
1025 param, fn_name
1026 )
1027 }
1028 IssueKind::InvalidPropertyFetch { ty } => {
1029 format!("Cannot fetch property on non-object type '{ty}'")
1030 }
1031 IssueKind::InvalidArrayAccess { ty } => {
1032 format!("Cannot use [] operator on non-array type '{ty}'")
1033 }
1034 IssueKind::InvalidArrayAssignment { ty } => {
1035 format!("Cannot use [] assignment on non-array type '{ty}'")
1036 }
1037 IssueKind::InvalidPropertyAssignment {
1038 property,
1039 expected,
1040 actual,
1041 } => {
1042 format!("Property ${property} expects '{expected}', cannot assign '{actual}'")
1043 }
1044 IssueKind::InvalidCast { from, to } => {
1045 format!("Cannot cast '{from}' to '{to}'")
1046 }
1047 IssueKind::InvalidStaticInvocation { class, method } => {
1048 format!("Non-static method {class}::{method}() cannot be called statically")
1049 }
1050 IssueKind::InvalidOperand { op, left, right } => {
1051 format!("Operator '{op}' not supported between '{left}' and '{right}'")
1052 }
1053 IssueKind::PossiblyInvalidOperand { op, left, right } => {
1054 format!("Operator '{op}' might not be supported between '{left}' and '{right}'")
1055 }
1056 IssueKind::PossiblyNullOperand { op, ty } => {
1057 format!("Operator '{op}' operand '{ty}' might be null")
1058 }
1059 IssueKind::MismatchingDocblockReturnType { declared, inferred } => {
1060 format!("Docblock return type '{declared}' does not match inferred '{inferred}'")
1061 }
1062 IssueKind::MismatchingDocblockParamType {
1063 param,
1064 declared,
1065 inferred,
1066 } => {
1067 format!(
1068 "Docblock type '{declared}' for ${param} does not match inferred '{inferred}'"
1069 )
1070 }
1071 IssueKind::TypeCheckMismatch {
1072 var,
1073 expected,
1074 actual,
1075 } => {
1076 format!("Type of ${var} is expected to be {expected}, got {actual}")
1077 }
1078
1079 IssueKind::InvalidArrayOffset { expected, actual } => {
1080 format!("Array offset expects '{expected}', got '{actual}'")
1081 }
1082 IssueKind::NonExistentArrayOffset { key } => {
1083 format!("Array offset '{key}' does not exist")
1084 }
1085 IssueKind::PossiblyInvalidArrayOffset { expected, actual } => {
1086 format!("Array offset might be invalid: expects '{expected}', got '{actual}'")
1087 }
1088
1089 IssueKind::RedundantCondition { ty } => {
1090 format!("Condition is always true/false for type '{ty}'")
1091 }
1092 IssueKind::RedundantCast { from, to } => {
1093 format!("Casting '{from}' to '{to}' is redundant")
1094 }
1095 IssueKind::UnnecessaryVarAnnotation { var } => {
1096 format!("@var annotation for ${var} is unnecessary")
1097 }
1098 IssueKind::TypeDoesNotContainType { left, right } => {
1099 format!("Type '{left}' can never contain type '{right}'")
1100 }
1101 IssueKind::ParadoxicalCondition { value } => {
1102 format!("Value {value} is duplicated; this branch can never be reached")
1103 }
1104 IssueKind::UnhandledMatchCondition { detail } => {
1105 format!("Unhandled match condition: {detail}")
1106 }
1107
1108 IssueKind::UnusedVariable { name } => format!("Variable ${name} is never read"),
1109 IssueKind::UnusedParam { name } => format!("Parameter ${name} is never used"),
1110 IssueKind::UnreachableCode => "Unreachable code detected".to_string(),
1111 IssueKind::UnusedMethod { class, method } => {
1112 format!("Private method {class}::{method}() is never called")
1113 }
1114 IssueKind::UnusedProperty { class, property } => {
1115 format!("Private property {class}::${property} is never read")
1116 }
1117 IssueKind::UnusedFunction { name } => {
1118 format!("Function {name}() is never called")
1119 }
1120 IssueKind::UnusedForeachValue { name } => {
1121 format!("Foreach value ${name} is never read")
1122 }
1123
1124 IssueKind::UnimplementedAbstractMethod { class, method } => {
1125 format!("Class {class} must implement abstract method {method}()")
1126 }
1127 IssueKind::UnimplementedInterfaceMethod {
1128 class,
1129 interface,
1130 method,
1131 } => {
1132 format!("Class {class} must implement {interface}::{method}() from interface")
1133 }
1134 IssueKind::MethodSignatureMismatch {
1135 class,
1136 method,
1137 detail,
1138 } => {
1139 format!("Method {class}::{method}() signature mismatch: {detail}")
1140 }
1141 IssueKind::OverriddenMethodAccess { class, method } => {
1142 format!("Method {class}::{method}() overrides with less visibility")
1143 }
1144 IssueKind::OverriddenPropertyAccess { class, property } => {
1145 format!("Property {class}::${property} overrides with less visibility")
1146 }
1147 IssueKind::ReadonlyPropertyAssignment { class, property } => {
1148 format!(
1149 "Cannot assign to readonly property {class}::${property} outside of constructor"
1150 )
1151 }
1152 IssueKind::InvalidExtendClass { parent, child } => {
1153 format!("Class {child} cannot extend final class {parent}")
1154 }
1155 IssueKind::InvalidTemplateParam {
1156 name,
1157 expected_bound,
1158 actual,
1159 } => {
1160 format!(
1161 "Template type '{name}' inferred as '{actual}' does not satisfy bound '{expected_bound}'"
1162 )
1163 }
1164 IssueKind::ShadowedTemplateParam { name } => {
1165 format!(
1166 "Method template parameter '{name}' shadows class-level template parameter with the same name"
1167 )
1168 }
1169 IssueKind::FinalMethodOverridden {
1170 class,
1171 method,
1172 parent,
1173 } => {
1174 format!("Method {class}::{method}() cannot override final method from {parent}")
1175 }
1176 IssueKind::AbstractInstantiation { class } => {
1177 format!("Cannot instantiate abstract class {class}")
1178 }
1179 IssueKind::AbstractMethodCall { class, method } => {
1180 format!("Cannot call abstract method {class}::{method}()")
1181 }
1182 IssueKind::InterfaceInstantiation { class } => {
1183 format!("Cannot instantiate interface {class}")
1184 }
1185 IssueKind::InvalidOverride {
1186 class,
1187 method,
1188 detail,
1189 } => {
1190 format!("Method {class}::{method}() has #[Override] but {detail}")
1191 }
1192
1193 IssueKind::TaintedInput { sink } => format!("Tainted input reaching sink '{sink}'"),
1194 IssueKind::TaintedHtml => "Tainted HTML output — possible XSS".to_string(),
1195 IssueKind::TaintedSql => "Tainted SQL query — possible SQL injection".to_string(),
1196 IssueKind::TaintedShell => {
1197 "Tainted shell command — possible command injection".to_string()
1198 }
1199
1200 IssueKind::DeprecatedCall { name, message } => {
1201 let base = format!("Call to deprecated function {name}");
1202 append_deprecation_message(base, message)
1203 }
1204 IssueKind::DeprecatedProperty {
1205 class,
1206 property,
1207 message,
1208 } => {
1209 let base = format!("Property {class}::${property} is deprecated");
1210 append_deprecation_message(base, message)
1211 }
1212 IssueKind::DeprecatedConstant {
1213 class,
1214 constant,
1215 message,
1216 } => {
1217 let base = format!("Constant {class}::{constant} is deprecated");
1218 append_deprecation_message(base, message)
1219 }
1220 IssueKind::DeprecatedInterface { name, message } => {
1221 let base = format!("Interface {name} is deprecated");
1222 append_deprecation_message(base, message)
1223 }
1224 IssueKind::DeprecatedTrait { name, message } => {
1225 let base = format!("Trait {name} is deprecated");
1226 append_deprecation_message(base, message)
1227 }
1228 IssueKind::DeprecatedMethodCall {
1229 class,
1230 method,
1231 message,
1232 } => {
1233 let base = format!("Call to deprecated method {class}::{method}");
1234 append_deprecation_message(base, message)
1235 }
1236 IssueKind::DeprecatedMethod {
1237 class,
1238 method,
1239 message,
1240 } => {
1241 let base = format!("Method {class}::{method}() is deprecated");
1242 append_deprecation_message(base, message)
1243 }
1244 IssueKind::DeprecatedClass { name, message } => {
1245 let base = format!("Class {name} is deprecated");
1246 append_deprecation_message(base, message)
1247 }
1248 IssueKind::InternalMethod { class, method } => {
1249 format!("Method {class}::{method}() is marked @internal")
1250 }
1251 IssueKind::MissingReturnType { fn_name } => {
1252 format!("Function {fn_name}() has no return type annotation")
1253 }
1254 IssueKind::MissingParamType { fn_name, param } => {
1255 format!("Parameter ${param} of {fn_name}() has no type annotation")
1256 }
1257 IssueKind::InvalidThrow { ty } => {
1258 format!("Thrown type '{ty}' does not extend Throwable")
1259 }
1260 IssueKind::InvalidCatch { ty } => {
1261 format!("Caught type '{ty}' does not extend Throwable")
1262 }
1263 IssueKind::MissingThrowsDocblock { class } => {
1264 format!("Exception {class} is thrown but not declared in @throws")
1265 }
1266 IssueKind::ImplicitToStringCast { class } => {
1267 format!("Class {class} is implicitly cast to string")
1268 }
1269 IssueKind::ImplicitFloatToIntCast { from } => {
1270 format!("Implicit cast from {from} to int truncates the fractional part")
1271 }
1272 IssueKind::ParseError { message } => format!("Parse error: {message}"),
1273 IssueKind::InvalidDocblock { message } => format!("Invalid docblock: {message}"),
1274 IssueKind::MixedArgument { param, fn_name } => {
1275 format!("Argument ${param} of {fn_name}() is mixed")
1276 }
1277 IssueKind::MixedAssignment { var } => {
1278 format!("Variable ${var} is assigned a mixed type")
1279 }
1280 IssueKind::MixedMethodCall { method } => {
1281 format!("Method {method}() called on mixed type")
1282 }
1283 IssueKind::MixedPropertyFetch { property } => {
1284 format!("Property ${property} fetched on mixed type")
1285 }
1286 IssueKind::MixedClone => "cannot clone mixed".to_string(),
1287 IssueKind::InvalidClone { ty } => format!("cannot clone non-object {ty}"),
1288 IssueKind::PossiblyInvalidClone { ty } => {
1289 format!("cannot clone possibly non-object {ty}")
1290 }
1291 IssueKind::InvalidToString { class } => {
1292 format!("Method {class}::__toString() must return a string")
1293 }
1294 IssueKind::CircularInheritance { class } => {
1295 format!("Class {class} has a circular inheritance chain")
1296 }
1297 IssueKind::InvalidTraitUse { trait_name, reason } => {
1298 format!("Trait {trait_name} used incorrectly: {reason}")
1299 }
1300 IssueKind::WrongCaseFunction { used, canonical } => {
1301 format!("Function name '{used}' has incorrect casing; use '{canonical}'")
1302 }
1303 IssueKind::WrongCaseMethod {
1304 class,
1305 used,
1306 canonical,
1307 } => {
1308 format!("Method name '{class}::{used}' has incorrect casing; use '{canonical}'")
1309 }
1310 IssueKind::WrongCaseClass { used, canonical } => {
1311 format!("Class name '{used}' has incorrect casing; use '{canonical}'")
1312 }
1313 IssueKind::InvalidAttribute { message } => message.clone(),
1314 IssueKind::UndefinedAttributeClass { name } => {
1315 format!("Attribute class {name} does not exist")
1316 }
1317 IssueKind::DuplicateClass { name } => {
1318 format!("Class {name} has already been defined")
1319 }
1320 }
1321 }
1322}
1323
1324#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1329pub struct Issue {
1330 pub kind: IssueKind,
1331 pub severity: Severity,
1332 pub location: Location,
1333 pub snippet: Option<String>,
1334 pub suppressed: bool,
1335}
1336
1337impl Issue {
1338 pub fn new(kind: IssueKind, location: Location) -> Self {
1339 let severity = kind.default_severity();
1340 Self {
1341 severity,
1342 kind,
1343 location,
1344 snippet: None,
1345 suppressed: false,
1346 }
1347 }
1348
1349 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
1350 self.snippet = Some(snippet.into());
1351 self
1352 }
1353
1354 pub fn suppress(mut self) -> Self {
1355 self.suppressed = true;
1356 self
1357 }
1358}
1359
1360impl fmt::Display for Issue {
1361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1362 let sev = match self.severity {
1363 Severity::Error => "error".red().to_string(),
1364 Severity::Warning => "warning".yellow().to_string(),
1365 Severity::Info => "info".blue().to_string(),
1366 };
1367 write!(
1368 f,
1369 "{} {}[{}] {}: {}",
1370 self.location.bright_black(),
1371 sev,
1372 self.kind.code().bright_black(),
1373 self.kind.name().bold(),
1374 self.kind.message()
1375 )
1376 }
1377}
1378
1379#[derive(Debug, Default)]
1384pub struct IssueBuffer {
1385 issues: Vec<Issue>,
1386 seen: HashSet<(&'static str, Arc<str>, u32, u16)>,
1387 file_suppressions: Vec<String>,
1389}
1390
1391impl IssueBuffer {
1392 pub fn new() -> Self {
1393 Self::default()
1394 }
1395
1396 pub fn add(&mut self, issue: Issue) {
1397 let key = (
1398 issue.kind.name(),
1399 issue.location.file.clone(),
1400 issue.location.line,
1401 issue.location.col_start,
1402 );
1403 if self.seen.insert(key) {
1404 self.issues.push(issue);
1405 }
1406 }
1407
1408 pub fn add_suppression(&mut self, name: impl Into<String>) {
1409 self.file_suppressions.push(name.into());
1410 }
1411
1412 pub fn into_issues(self) -> Vec<Issue> {
1414 self.issues
1415 .into_iter()
1416 .filter(|i| !i.suppressed)
1417 .filter(|i| !self.file_suppressions.contains(&i.kind.name().to_string()))
1418 .collect()
1419 }
1420
1421 pub fn suppress_range(&mut self, from: usize, suppressions: &[String]) {
1424 if suppressions.is_empty() {
1425 return;
1426 }
1427 for issue in self.issues[from..].iter_mut() {
1428 if suppressions.iter().any(|s| s == issue.kind.name()) {
1429 issue.suppressed = true;
1430 }
1431 }
1432 }
1433
1434 pub fn issue_count(&self) -> usize {
1437 self.issues.len()
1438 }
1439
1440 pub fn is_empty(&self) -> bool {
1441 self.issues.is_empty()
1442 }
1443
1444 pub fn len(&self) -> usize {
1445 self.issues.len()
1446 }
1447
1448 pub fn error_count(&self) -> usize {
1449 self.issues
1450 .iter()
1451 .filter(|i| !i.suppressed && i.severity == Severity::Error)
1452 .count()
1453 }
1454
1455 pub fn warning_count(&self) -> usize {
1456 self.issues
1457 .iter()
1458 .filter(|i| !i.suppressed && i.severity == Severity::Warning)
1459 .count()
1460 }
1461}
1462
1463#[cfg(test)]
1464mod code_tests {
1465 use super::*;
1466 use std::collections::HashSet;
1467
1468 fn one_of_each() -> Vec<IssueKind> {
1474 let s = || String::new();
1475 vec![
1476 IssueKind::InvalidScope { in_class: false },
1477 IssueKind::NonStaticSelfCall {
1478 class: s(),
1479 method: s(),
1480 },
1481 IssueKind::DirectConstructorCall { class: s() },
1482 IssueKind::UndefinedVariable { name: s() },
1483 IssueKind::UndefinedFunction { name: s() },
1484 IssueKind::UndefinedMethod {
1485 class: s(),
1486 method: s(),
1487 },
1488 IssueKind::UndefinedClass { name: s() },
1489 IssueKind::UndefinedProperty {
1490 class: s(),
1491 property: s(),
1492 },
1493 IssueKind::UndefinedConstant { name: s() },
1494 IssueKind::InaccessibleClassConstant {
1495 class: s(),
1496 constant: s(),
1497 },
1498 IssueKind::PossiblyUndefinedVariable { name: s() },
1499 IssueKind::UndefinedTrait { name: s() },
1500 IssueKind::ParentNotFound,
1501 IssueKind::NullArgument {
1502 param: s(),
1503 fn_name: s(),
1504 },
1505 IssueKind::NullPropertyFetch { property: s() },
1506 IssueKind::NullMethodCall { method: s() },
1507 IssueKind::NullArrayAccess,
1508 IssueKind::PossiblyNullArgument {
1509 param: s(),
1510 fn_name: s(),
1511 },
1512 IssueKind::PossiblyInvalidArgument {
1513 param: s(),
1514 fn_name: s(),
1515 expected: s(),
1516 actual: s(),
1517 },
1518 IssueKind::PossiblyNullPropertyFetch { property: s() },
1519 IssueKind::PossiblyNullMethodCall { method: s() },
1520 IssueKind::PossiblyNullArrayAccess,
1521 IssueKind::NullableReturnStatement {
1522 expected: s(),
1523 actual: s(),
1524 },
1525 IssueKind::InvalidReturnType {
1526 expected: s(),
1527 actual: s(),
1528 },
1529 IssueKind::InvalidArgument {
1530 param: s(),
1531 fn_name: s(),
1532 expected: s(),
1533 actual: s(),
1534 },
1535 IssueKind::TooFewArguments {
1536 fn_name: s(),
1537 expected: 0,
1538 actual: 0,
1539 },
1540 IssueKind::TooManyArguments {
1541 fn_name: s(),
1542 expected: 0,
1543 actual: 0,
1544 },
1545 IssueKind::InvalidNamedArgument {
1546 fn_name: s(),
1547 name: s(),
1548 },
1549 IssueKind::InvalidPassByReference {
1550 fn_name: s(),
1551 param: s(),
1552 },
1553 IssueKind::InvalidPropertyFetch { ty: s() },
1554 IssueKind::InvalidArrayAccess { ty: s() },
1555 IssueKind::InvalidArrayAssignment { ty: s() },
1556 IssueKind::InvalidPropertyAssignment {
1557 property: s(),
1558 expected: s(),
1559 actual: s(),
1560 },
1561 IssueKind::InvalidCast { from: s(), to: s() },
1562 IssueKind::InvalidStaticInvocation {
1563 class: s(),
1564 method: s(),
1565 },
1566 IssueKind::InvalidOperand {
1567 op: s(),
1568 left: s(),
1569 right: s(),
1570 },
1571 IssueKind::PossiblyInvalidOperand {
1572 op: s(),
1573 left: s(),
1574 right: s(),
1575 },
1576 IssueKind::PossiblyNullOperand { op: s(), ty: s() },
1577 IssueKind::MismatchingDocblockReturnType {
1578 declared: s(),
1579 inferred: s(),
1580 },
1581 IssueKind::MismatchingDocblockParamType {
1582 param: s(),
1583 declared: s(),
1584 inferred: s(),
1585 },
1586 IssueKind::TypeCheckMismatch {
1587 var: s(),
1588 expected: s(),
1589 actual: s(),
1590 },
1591 IssueKind::InvalidArrayOffset {
1592 expected: s(),
1593 actual: s(),
1594 },
1595 IssueKind::NonExistentArrayOffset { key: s() },
1596 IssueKind::PossiblyInvalidArrayOffset {
1597 expected: s(),
1598 actual: s(),
1599 },
1600 IssueKind::RedundantCondition { ty: s() },
1601 IssueKind::RedundantCast { from: s(), to: s() },
1602 IssueKind::UnnecessaryVarAnnotation { var: s() },
1603 IssueKind::TypeDoesNotContainType {
1604 left: s(),
1605 right: s(),
1606 },
1607 IssueKind::UnusedVariable { name: s() },
1608 IssueKind::UnusedParam { name: s() },
1609 IssueKind::UnreachableCode,
1610 IssueKind::UnhandledMatchCondition { detail: s() },
1611 IssueKind::UnusedMethod {
1612 class: s(),
1613 method: s(),
1614 },
1615 IssueKind::UnusedProperty {
1616 class: s(),
1617 property: s(),
1618 },
1619 IssueKind::UnusedFunction { name: s() },
1620 IssueKind::UnusedForeachValue { name: s() },
1621 IssueKind::ReadonlyPropertyAssignment {
1622 class: s(),
1623 property: s(),
1624 },
1625 IssueKind::UnimplementedAbstractMethod {
1626 class: s(),
1627 method: s(),
1628 },
1629 IssueKind::UnimplementedInterfaceMethod {
1630 class: s(),
1631 interface: s(),
1632 method: s(),
1633 },
1634 IssueKind::MethodSignatureMismatch {
1635 class: s(),
1636 method: s(),
1637 detail: s(),
1638 },
1639 IssueKind::OverriddenMethodAccess {
1640 class: s(),
1641 method: s(),
1642 },
1643 IssueKind::OverriddenPropertyAccess {
1644 class: s(),
1645 property: s(),
1646 },
1647 IssueKind::InvalidExtendClass {
1648 parent: s(),
1649 child: s(),
1650 },
1651 IssueKind::FinalMethodOverridden {
1652 class: s(),
1653 method: s(),
1654 parent: s(),
1655 },
1656 IssueKind::AbstractInstantiation { class: s() },
1657 IssueKind::AbstractMethodCall {
1658 class: s(),
1659 method: s(),
1660 },
1661 IssueKind::InterfaceInstantiation { class: s() },
1662 IssueKind::InvalidOverride {
1663 class: s(),
1664 method: s(),
1665 detail: s(),
1666 },
1667 IssueKind::CircularInheritance { class: s() },
1668 IssueKind::TaintedInput { sink: s() },
1669 IssueKind::TaintedHtml,
1670 IssueKind::TaintedSql,
1671 IssueKind::TaintedShell,
1672 IssueKind::InvalidTemplateParam {
1673 name: s(),
1674 expected_bound: s(),
1675 actual: s(),
1676 },
1677 IssueKind::ShadowedTemplateParam { name: s() },
1678 IssueKind::DeprecatedCall {
1679 name: s(),
1680 message: None,
1681 },
1682 IssueKind::DeprecatedProperty {
1683 class: s(),
1684 property: s(),
1685 message: None,
1686 },
1687 IssueKind::DeprecatedConstant {
1688 class: s(),
1689 constant: s(),
1690 message: None,
1691 },
1692 IssueKind::DeprecatedInterface {
1693 name: s(),
1694 message: None,
1695 },
1696 IssueKind::DeprecatedTrait {
1697 name: s(),
1698 message: None,
1699 },
1700 IssueKind::DeprecatedMethodCall {
1701 class: s(),
1702 method: s(),
1703 message: None,
1704 },
1705 IssueKind::DeprecatedMethod {
1706 class: s(),
1707 method: s(),
1708 message: None,
1709 },
1710 IssueKind::DeprecatedClass {
1711 name: s(),
1712 message: None,
1713 },
1714 IssueKind::InternalMethod {
1715 class: s(),
1716 method: s(),
1717 },
1718 IssueKind::MissingReturnType { fn_name: s() },
1719 IssueKind::MissingParamType {
1720 fn_name: s(),
1721 param: s(),
1722 },
1723 IssueKind::MissingThrowsDocblock { class: s() },
1724 IssueKind::InvalidDocblock { message: s() },
1725 IssueKind::MixedArgument {
1726 param: s(),
1727 fn_name: s(),
1728 },
1729 IssueKind::MixedAssignment { var: s() },
1730 IssueKind::MixedMethodCall { method: s() },
1731 IssueKind::MixedPropertyFetch { property: s() },
1732 IssueKind::MixedClone,
1733 IssueKind::InvalidClone { ty: s() },
1734 IssueKind::PossiblyInvalidClone { ty: s() },
1735 IssueKind::InvalidToString { class: s() },
1736 IssueKind::InvalidTraitUse {
1737 trait_name: s(),
1738 reason: s(),
1739 },
1740 IssueKind::ParseError { message: s() },
1741 IssueKind::InvalidThrow { ty: s() },
1742 IssueKind::InvalidCatch { ty: s() },
1743 IssueKind::ImplicitToStringCast { class: s() },
1744 IssueKind::ImplicitFloatToIntCast { from: s() },
1745 IssueKind::WrongCaseFunction {
1746 used: s(),
1747 canonical: s(),
1748 },
1749 IssueKind::WrongCaseMethod {
1750 class: s(),
1751 used: s(),
1752 canonical: s(),
1753 },
1754 IssueKind::WrongCaseClass {
1755 used: s(),
1756 canonical: s(),
1757 },
1758 IssueKind::InvalidAttribute { message: s() },
1759 IssueKind::UndefinedAttributeClass { name: s() },
1760 IssueKind::DuplicateClass { name: s() },
1761 ]
1762 }
1763
1764 #[test]
1765 fn codes_have_expected_shape() {
1766 for kind in one_of_each() {
1767 let code = kind.code();
1768 assert!(
1769 code.len() == 7
1770 && code.starts_with("MIR")
1771 && code[3..].chars().all(|c| c.is_ascii_digit()),
1772 "code {code:?} for {} does not match MIR####",
1773 kind.name(),
1774 );
1775 }
1776 }
1777
1778 #[test]
1779 fn codes_are_unique() {
1780 let kinds = one_of_each();
1781 let mut seen: HashSet<&'static str> = HashSet::new();
1782 for kind in &kinds {
1783 assert!(
1784 seen.insert(kind.code()),
1785 "duplicate code {} (variant {})",
1786 kind.code(),
1787 kind.name(),
1788 );
1789 }
1790 }
1791
1792 #[test]
1793 fn display_includes_code() {
1794 let issue = Issue::new(
1795 IssueKind::UndefinedClass {
1796 name: "Foo".to_string(),
1797 },
1798 Location {
1799 file: Arc::from("src/x.php"),
1800 line: 1,
1801 line_end: 1,
1802 col_start: 0,
1803 col_end: 3,
1804 },
1805 );
1806 let raw = format!("{issue}");
1809 let stripped: String = {
1810 let mut out = String::new();
1811 let mut chars = raw.chars();
1812 while let Some(c) = chars.next() {
1813 if c == '\u{1b}' {
1814 for c2 in chars.by_ref() {
1815 if c2 == 'm' {
1816 break;
1817 }
1818 }
1819 } else {
1820 out.push(c);
1821 }
1822 }
1823 out
1824 };
1825 assert!(
1826 stripped.contains("error[MIR0005] UndefinedClass:"),
1827 "Display output missing code/name segment: {stripped:?}",
1828 );
1829 }
1830
1831 #[test]
1834 fn one_of_each_has_every_variant() {
1835 assert_eq!(one_of_each().len(), 108);
1839 }
1840}