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