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 PossiblyUndefinedVariable { name: String },
76 UndefinedTrait { name: String },
79 InvalidStringClass { actual: String },
82
83 NullArgument { param: String, fn_name: String },
87 NullPropertyFetch { property: String },
90 NullMethodCall { method: String },
93 NullArrayAccess,
96 PossiblyNullArgument { param: String, fn_name: String },
99 PossiblyInvalidArgument {
102 param: String,
103 fn_name: String,
104 expected: String,
105 actual: String,
106 },
107 PossiblyNullPropertyFetch { property: String },
110 PossiblyNullMethodCall { method: String },
113 PossiblyNullArrayAccess,
116 NullableReturnStatement { expected: String, actual: String },
118
119 InvalidReturnType { expected: String, actual: String },
123 InvalidArgument {
126 param: String,
127 fn_name: String,
128 expected: String,
129 actual: String,
130 },
131 TooFewArguments {
134 fn_name: String,
135 expected: usize,
136 actual: usize,
137 },
138 TooManyArguments {
141 fn_name: String,
142 expected: usize,
143 actual: usize,
144 },
145 InvalidNamedArgument { fn_name: String, name: String },
148 InvalidPassByReference { fn_name: String, param: String },
151 InvalidPropertyFetch { ty: String },
154 InvalidArrayAccess { ty: String },
157 InvalidArrayAssignment { ty: String },
160 InvalidPropertyAssignment {
163 property: String,
164 expected: String,
165 actual: String,
166 },
167 InvalidCast { from: String, to: String },
170 InvalidStaticInvocation { class: String, method: String },
173 InvalidOperand {
177 op: String,
178 left: String,
179 right: String,
180 },
181 PossiblyInvalidOperand {
184 op: String,
185 left: String,
186 right: String,
187 },
188 PossiblyNullOperand { op: String, ty: String },
191 MismatchingDocblockReturnType { declared: String, inferred: String },
193 MismatchingDocblockParamType {
195 param: String,
196 declared: String,
197 inferred: String,
198 },
199 TypeCheckMismatch {
202 var: String,
203 expected: String,
204 actual: String,
205 },
206
207 InvalidArrayOffset { expected: String, actual: String },
210 NonExistentArrayOffset { key: String },
212 PossiblyInvalidArrayOffset { expected: String, actual: String },
215
216 RedundantCondition { ty: String },
220 RedundantCast { from: String, to: String },
223 UnnecessaryVarAnnotation { var: String },
225 TypeDoesNotContainType { left: String, right: String },
227 ParadoxicalCondition { value: String },
230
231 UnusedVariable { name: String },
235 UnusedParam { name: String },
238 UnreachableCode,
241 UnusedMethod { class: String, method: String },
244 UnusedProperty { class: String, property: String },
247 UnusedFunction { name: String },
250 UnusedForeachValue { name: String },
253
254 ReadonlyPropertyAssignment { class: String, property: String },
258
259 UnimplementedAbstractMethod { class: String, method: String },
263 UnimplementedInterfaceMethod {
266 class: String,
267 interface: String,
268 method: String,
269 },
270 MethodSignatureMismatch {
273 class: String,
274 method: String,
275 detail: String,
276 },
277 OverriddenMethodAccess { class: String, method: String },
280 DirectConstructorCall { class: String },
283 FinalClassExtended { parent: String, child: String },
286 FinalMethodOverridden {
289 class: String,
290 method: String,
291 parent: String,
292 },
293 AbstractInstantiation { class: String },
296 InterfaceInstantiation { class: String },
299 InvalidOverride {
303 class: String,
304 method: String,
305 detail: String,
306 },
307
308 TaintedInput { sink: String },
312 TaintedHtml,
315 TaintedSql,
318 TaintedShell,
321
322 InvalidTemplateParam {
326 name: String,
327 expected_bound: String,
328 actual: String,
329 },
330 ShadowedTemplateParam { name: String },
333
334 DeprecatedCall {
338 name: String,
339 message: Option<Arc<str>>,
340 },
341 DeprecatedProperty {
344 class: String,
345 property: String,
346 message: Option<Arc<str>>,
347 },
348 DeprecatedConstant {
351 class: String,
352 constant: String,
353 message: Option<Arc<str>>,
354 },
355 DeprecatedInterface {
358 name: String,
359 message: Option<Arc<str>>,
360 },
361 DeprecatedTrait {
364 name: String,
365 message: Option<Arc<str>>,
366 },
367 DeprecatedMethodCall {
370 class: String,
371 method: String,
372 message: Option<Arc<str>>,
373 },
374 DeprecatedMethod {
377 class: String,
378 method: String,
379 message: Option<Arc<str>>,
380 },
381 DeprecatedClass {
384 name: String,
385 message: Option<Arc<str>>,
386 },
387 InternalMethod { class: String, method: String },
390 MissingReturnType { fn_name: String },
392 MissingParamType { fn_name: String, param: String },
394 InvalidThrow { ty: String },
397 InvalidCatch { ty: String },
400 MissingThrowsDocblock { class: String },
403 ImplicitToStringCast { class: String },
406 ImplicitFloatToIntCast { from: String },
409 ParseError { message: String },
412 InvalidDocblock { message: String },
415 MixedArgument { param: String, fn_name: String },
417 MixedAssignment { var: String },
419 MixedMethodCall { method: String },
422 MixedPropertyFetch { property: String },
424 MixedClone,
427 InvalidClone { ty: String },
431 PossiblyInvalidClone { ty: String },
435 InvalidToString { class: String },
439 CircularInheritance { class: String },
442
443 InvalidTraitUse { trait_name: String, reason: String },
447}
448
449fn append_deprecation_message(base: String, message: &Option<Arc<str>>) -> String {
450 match message.as_deref().filter(|m| !m.is_empty()) {
451 Some(msg) => format!("{base}: {msg}"),
452 None => base,
453 }
454}
455
456impl IssueKind {
457 pub fn default_severity(&self) -> Severity {
459 match self {
460 IssueKind::NonStaticSelfCall { .. }
462 | IssueKind::DirectConstructorCall { .. }
463 | IssueKind::InvalidScope { .. }
464 | IssueKind::UndefinedVariable { .. }
465 | IssueKind::UndefinedFunction { .. }
466 | IssueKind::UndefinedMethod { .. }
467 | IssueKind::UndefinedClass { .. }
468 | IssueKind::UndefinedConstant { .. }
469 | IssueKind::InvalidReturnType { .. }
470 | IssueKind::InvalidArgument { .. }
471 | IssueKind::TooFewArguments { .. }
472 | IssueKind::TooManyArguments { .. }
473 | IssueKind::InvalidNamedArgument { .. }
474 | IssueKind::InvalidPassByReference { .. }
475 | IssueKind::InvalidThrow { .. }
476 | IssueKind::InvalidCatch { .. }
477 | IssueKind::InvalidStaticInvocation { .. }
478 | IssueKind::UnimplementedAbstractMethod { .. }
479 | IssueKind::UnimplementedInterfaceMethod { .. }
480 | IssueKind::MethodSignatureMismatch { .. }
481 | IssueKind::FinalClassExtended { .. }
482 | IssueKind::FinalMethodOverridden { .. }
483 | IssueKind::AbstractInstantiation { .. }
484 | IssueKind::InterfaceInstantiation { .. }
485 | IssueKind::InvalidOverride { .. }
486 | IssueKind::InvalidTemplateParam { .. }
487 | IssueKind::ReadonlyPropertyAssignment { .. }
488 | IssueKind::ParseError { .. }
489 | IssueKind::TaintedInput { .. }
490 | IssueKind::TaintedHtml
491 | IssueKind::TaintedSql
492 | IssueKind::TaintedShell
493 | IssueKind::CircularInheritance { .. }
494 | IssueKind::InvalidTraitUse { .. }
495 | IssueKind::UndefinedTrait { .. }
496 | IssueKind::InvalidClone { .. }
497 | IssueKind::InvalidToString { .. }
498 | IssueKind::TypeCheckMismatch { .. } => Severity::Error,
499
500 IssueKind::NullArgument { .. }
502 | IssueKind::NullPropertyFetch { .. }
503 | IssueKind::NullMethodCall { .. }
504 | IssueKind::NullArrayAccess
505 | IssueKind::NullableReturnStatement { .. }
506 | IssueKind::InvalidPropertyFetch { .. }
507 | IssueKind::InvalidArrayAccess { .. }
508 | IssueKind::InvalidArrayAssignment { .. }
509 | IssueKind::InvalidPropertyAssignment { .. }
510 | IssueKind::InvalidArrayOffset { .. }
511 | IssueKind::NonExistentArrayOffset { .. }
512 | IssueKind::PossiblyInvalidArrayOffset { .. }
513 | IssueKind::UndefinedProperty { .. }
514 | IssueKind::InvalidOperand { .. }
515 | IssueKind::OverriddenMethodAccess { .. }
516 | IssueKind::ImplicitToStringCast { .. }
517 | IssueKind::ImplicitFloatToIntCast { .. }
518 | IssueKind::UnusedVariable { .. }
519 | IssueKind::UnusedForeachValue { .. }
520 | IssueKind::ParadoxicalCondition { .. }
521 | IssueKind::InvalidStringClass { .. } => Severity::Warning,
522
523 IssueKind::PossiblyUndefinedVariable { .. } => Severity::Warning,
525
526 IssueKind::PossiblyNullArgument { .. }
528 | IssueKind::PossiblyInvalidArgument { .. }
529 | IssueKind::PossiblyNullPropertyFetch { .. }
530 | IssueKind::PossiblyNullMethodCall { .. }
531 | IssueKind::PossiblyNullArrayAccess
532 | IssueKind::PossiblyInvalidClone { .. }
533 | IssueKind::PossiblyInvalidOperand { .. }
534 | IssueKind::PossiblyNullOperand { .. } => Severity::Info,
535
536 IssueKind::RedundantCondition { .. }
538 | IssueKind::RedundantCast { .. }
539 | IssueKind::UnnecessaryVarAnnotation { .. }
540 | IssueKind::TypeDoesNotContainType { .. }
541 | IssueKind::UnusedParam { .. }
542 | IssueKind::UnreachableCode
543 | IssueKind::UnusedMethod { .. }
544 | IssueKind::UnusedProperty { .. }
545 | IssueKind::UnusedFunction { .. }
546 | IssueKind::DeprecatedCall { .. }
547 | IssueKind::DeprecatedProperty { .. }
548 | IssueKind::DeprecatedConstant { .. }
549 | IssueKind::DeprecatedInterface { .. }
550 | IssueKind::DeprecatedTrait { .. }
551 | IssueKind::DeprecatedMethodCall { .. }
552 | IssueKind::DeprecatedMethod { .. }
553 | IssueKind::DeprecatedClass { .. }
554 | IssueKind::InternalMethod { .. }
555 | IssueKind::MissingReturnType { .. }
556 | IssueKind::MissingParamType { .. }
557 | IssueKind::MismatchingDocblockReturnType { .. }
558 | IssueKind::MismatchingDocblockParamType { .. }
559 | IssueKind::InvalidDocblock { .. }
560 | IssueKind::InvalidCast { .. }
561 | IssueKind::MixedArgument { .. }
562 | IssueKind::MixedAssignment { .. }
563 | IssueKind::MixedMethodCall { .. }
564 | IssueKind::MixedPropertyFetch { .. }
565 | IssueKind::MixedClone
566 | IssueKind::ShadowedTemplateParam { .. }
567 | IssueKind::MissingThrowsDocblock { .. } => Severity::Info,
568 }
569 }
570
571 pub fn code(&self) -> &'static str {
599 match self {
600 IssueKind::NonStaticSelfCall { .. } => "MIR0216",
602 IssueKind::DirectConstructorCall { .. } => "MIR0217",
603 IssueKind::InvalidScope { .. } => "MIR0001",
604 IssueKind::UndefinedVariable { .. } => "MIR0002",
605 IssueKind::UndefinedFunction { .. } => "MIR0003",
606 IssueKind::UndefinedMethod { .. } => "MIR0004",
607 IssueKind::UndefinedClass { .. } => "MIR0005",
608 IssueKind::UndefinedProperty { .. } => "MIR0006",
609 IssueKind::UndefinedConstant { .. } => "MIR0007",
610 IssueKind::PossiblyUndefinedVariable { .. } => "MIR0008",
611 IssueKind::UndefinedTrait { .. } => "MIR0009",
612
613 IssueKind::NullArgument { .. } => "MIR0100",
615 IssueKind::NullPropertyFetch { .. } => "MIR0101",
616 IssueKind::NullMethodCall { .. } => "MIR0102",
617 IssueKind::NullArrayAccess => "MIR0103",
618 IssueKind::PossiblyNullArgument { .. } => "MIR0104",
619 IssueKind::PossiblyInvalidArgument { .. } => "MIR0105",
620 IssueKind::PossiblyNullPropertyFetch { .. } => "MIR0106",
621 IssueKind::PossiblyNullMethodCall { .. } => "MIR0107",
622 IssueKind::PossiblyNullArrayAccess => "MIR0108",
623 IssueKind::NullableReturnStatement { .. } => "MIR0109",
624
625 IssueKind::InvalidReturnType { .. } => "MIR0200",
627 IssueKind::InvalidArgument { .. } => "MIR0201",
628 IssueKind::TooFewArguments { .. } => "MIR0202",
629 IssueKind::TooManyArguments { .. } => "MIR0203",
630 IssueKind::InvalidNamedArgument { .. } => "MIR0204",
631 IssueKind::InvalidPassByReference { .. } => "MIR0205",
632 IssueKind::InvalidPropertyFetch { .. } => "MIR0218",
633 IssueKind::InvalidArrayAccess { .. } => "MIR0219",
634 IssueKind::InvalidArrayAssignment { .. } => "MIR0220",
635 IssueKind::InvalidPropertyAssignment { .. } => "MIR0206",
636 IssueKind::InvalidCast { .. } => "MIR0207",
637 IssueKind::InvalidStaticInvocation { .. } => "MIR0215",
638 IssueKind::InvalidOperand { .. } => "MIR0208",
639 IssueKind::PossiblyInvalidOperand { .. } => "MIR0213",
640 IssueKind::PossiblyNullOperand { .. } => "MIR0214",
641 IssueKind::MismatchingDocblockReturnType { .. } => "MIR0209",
642 IssueKind::MismatchingDocblockParamType { .. } => "MIR0210",
643 IssueKind::InvalidStringClass { .. } => "MIR0211",
644 IssueKind::TypeCheckMismatch { .. } => "MIR0212",
645
646 IssueKind::InvalidArrayOffset { .. } => "MIR0300",
648 IssueKind::NonExistentArrayOffset { .. } => "MIR0301",
649 IssueKind::PossiblyInvalidArrayOffset { .. } => "MIR0302",
650
651 IssueKind::RedundantCondition { .. } => "MIR0400",
653 IssueKind::RedundantCast { .. } => "MIR0401",
654 IssueKind::UnnecessaryVarAnnotation { .. } => "MIR0402",
655 IssueKind::TypeDoesNotContainType { .. } => "MIR0403",
656 IssueKind::ParadoxicalCondition { .. } => "MIR0404",
657
658 IssueKind::UnusedVariable { .. } => "MIR0500",
660 IssueKind::UnusedParam { .. } => "MIR0501",
661 IssueKind::UnreachableCode => "MIR0502",
662 IssueKind::UnusedMethod { .. } => "MIR0503",
663 IssueKind::UnusedProperty { .. } => "MIR0504",
664 IssueKind::UnusedFunction { .. } => "MIR0505",
665 IssueKind::UnusedForeachValue { .. } => "MIR0506",
666
667 IssueKind::ReadonlyPropertyAssignment { .. } => "MIR0600",
669
670 IssueKind::UnimplementedAbstractMethod { .. } => "MIR0700",
672 IssueKind::UnimplementedInterfaceMethod { .. } => "MIR0701",
673 IssueKind::MethodSignatureMismatch { .. } => "MIR0702",
674 IssueKind::OverriddenMethodAccess { .. } => "MIR0703",
675 IssueKind::FinalClassExtended { .. } => "MIR0704",
676 IssueKind::FinalMethodOverridden { .. } => "MIR0705",
677 IssueKind::AbstractInstantiation { .. } => "MIR0706",
678 IssueKind::InterfaceInstantiation { .. } => "MIR0709",
679 IssueKind::CircularInheritance { .. } => "MIR0707",
680 IssueKind::InvalidOverride { .. } => "MIR0708",
681
682 IssueKind::TaintedInput { .. } => "MIR0800",
684 IssueKind::TaintedHtml => "MIR0801",
685 IssueKind::TaintedSql => "MIR0802",
686 IssueKind::TaintedShell => "MIR0803",
687
688 IssueKind::InvalidTemplateParam { .. } => "MIR0900",
690 IssueKind::ShadowedTemplateParam { .. } => "MIR0901",
691
692 IssueKind::DeprecatedCall { .. } => "MIR1000",
694 IssueKind::DeprecatedProperty { .. } => "MIR1005",
695 IssueKind::DeprecatedInterface { .. } => "MIR1006",
696 IssueKind::DeprecatedTrait { .. } => "MIR1007",
697 IssueKind::DeprecatedConstant { .. } => "MIR1008",
698 IssueKind::DeprecatedMethodCall { .. } => "MIR1001",
699 IssueKind::DeprecatedMethod { .. } => "MIR1002",
700 IssueKind::DeprecatedClass { .. } => "MIR1003",
701 IssueKind::InternalMethod { .. } => "MIR1004",
702
703 IssueKind::MissingReturnType { .. } => "MIR1100",
705 IssueKind::MissingParamType { .. } => "MIR1101",
706 IssueKind::MissingThrowsDocblock { .. } => "MIR1102",
707 IssueKind::InvalidDocblock { .. } => "MIR1103",
708
709 IssueKind::MixedArgument { .. } => "MIR1200",
711 IssueKind::MixedAssignment { .. } => "MIR1201",
712 IssueKind::MixedMethodCall { .. } => "MIR1202",
713 IssueKind::MixedPropertyFetch { .. } => "MIR1203",
714 IssueKind::MixedClone => "MIR1204",
715 IssueKind::InvalidClone { .. } => "MIR1205",
716 IssueKind::PossiblyInvalidClone { .. } => "MIR1206",
717 IssueKind::InvalidToString { .. } => "MIR1207",
718
719 IssueKind::InvalidTraitUse { .. } => "MIR1300",
721
722 IssueKind::ParseError { .. } => "MIR1400",
724
725 IssueKind::InvalidThrow { .. } => "MIR1500",
727 IssueKind::InvalidCatch { .. } => "MIR1503",
728 IssueKind::ImplicitToStringCast { .. } => "MIR1501",
729 IssueKind::ImplicitFloatToIntCast { .. } => "MIR1502",
730 }
731 }
732
733 pub fn name(&self) -> &'static str {
735 match self {
736 IssueKind::NonStaticSelfCall { .. } => "NonStaticSelfCall",
737 IssueKind::DirectConstructorCall { .. } => "DirectConstructorCall",
738 IssueKind::InvalidScope { .. } => "InvalidScope",
739 IssueKind::UndefinedVariable { .. } => "UndefinedVariable",
740 IssueKind::UndefinedFunction { .. } => "UndefinedFunction",
741 IssueKind::UndefinedMethod { .. } => "UndefinedMethod",
742 IssueKind::UndefinedClass { .. } => "UndefinedClass",
743 IssueKind::UndefinedProperty { .. } => "UndefinedProperty",
744 IssueKind::UndefinedConstant { .. } => "UndefinedConstant",
745 IssueKind::PossiblyUndefinedVariable { .. } => "PossiblyUndefinedVariable",
746 IssueKind::UndefinedTrait { .. } => "UndefinedTrait",
747 IssueKind::InvalidStringClass { .. } => "InvalidStringClass",
748 IssueKind::NullArgument { .. } => "NullArgument",
749 IssueKind::NullPropertyFetch { .. } => "NullPropertyFetch",
750 IssueKind::NullMethodCall { .. } => "NullMethodCall",
751 IssueKind::NullArrayAccess => "NullArrayAccess",
752 IssueKind::PossiblyNullArgument { .. } => "PossiblyNullArgument",
753 IssueKind::PossiblyInvalidArgument { .. } => "PossiblyInvalidArgument",
754 IssueKind::PossiblyNullPropertyFetch { .. } => "PossiblyNullPropertyFetch",
755 IssueKind::PossiblyNullMethodCall { .. } => "PossiblyNullMethodCall",
756 IssueKind::PossiblyNullArrayAccess => "PossiblyNullArrayAccess",
757 IssueKind::NullableReturnStatement { .. } => "NullableReturnStatement",
758 IssueKind::InvalidReturnType { .. } => "InvalidReturnType",
759 IssueKind::InvalidArgument { .. } => "InvalidArgument",
760 IssueKind::TooFewArguments { .. } => "TooFewArguments",
761 IssueKind::TooManyArguments { .. } => "TooManyArguments",
762 IssueKind::InvalidNamedArgument { .. } => "InvalidNamedArgument",
763 IssueKind::InvalidPassByReference { .. } => "InvalidPassByReference",
764 IssueKind::InvalidPropertyFetch { .. } => "InvalidPropertyFetch",
765 IssueKind::InvalidArrayAccess { .. } => "InvalidArrayAccess",
766 IssueKind::InvalidArrayAssignment { .. } => "InvalidArrayAssignment",
767 IssueKind::InvalidPropertyAssignment { .. } => "InvalidPropertyAssignment",
768 IssueKind::InvalidCast { .. } => "InvalidCast",
769 IssueKind::InvalidStaticInvocation { .. } => "InvalidStaticInvocation",
770 IssueKind::InvalidOperand { .. } => "InvalidOperand",
771 IssueKind::PossiblyInvalidOperand { .. } => "PossiblyInvalidOperand",
772 IssueKind::PossiblyNullOperand { .. } => "PossiblyNullOperand",
773 IssueKind::MismatchingDocblockReturnType { .. } => "MismatchingDocblockReturnType",
774 IssueKind::MismatchingDocblockParamType { .. } => "MismatchingDocblockParamType",
775 IssueKind::TypeCheckMismatch { .. } => "TypeCheckMismatch",
776 IssueKind::InvalidArrayOffset { .. } => "InvalidArrayOffset",
777 IssueKind::NonExistentArrayOffset { .. } => "NonExistentArrayOffset",
778 IssueKind::PossiblyInvalidArrayOffset { .. } => "PossiblyInvalidArrayOffset",
779 IssueKind::RedundantCondition { .. } => "RedundantCondition",
780 IssueKind::RedundantCast { .. } => "RedundantCast",
781 IssueKind::UnnecessaryVarAnnotation { .. } => "UnnecessaryVarAnnotation",
782 IssueKind::TypeDoesNotContainType { .. } => "TypeDoesNotContainType",
783 IssueKind::ParadoxicalCondition { .. } => "ParadoxicalCondition",
784 IssueKind::UnusedVariable { .. } => "UnusedVariable",
785 IssueKind::UnusedParam { .. } => "UnusedParam",
786 IssueKind::UnreachableCode => "UnreachableCode",
787 IssueKind::UnusedMethod { .. } => "UnusedMethod",
788 IssueKind::UnusedProperty { .. } => "UnusedProperty",
789 IssueKind::UnusedFunction { .. } => "UnusedFunction",
790 IssueKind::UnusedForeachValue { .. } => "UnusedForeachValue",
791 IssueKind::UnimplementedAbstractMethod { .. } => "UnimplementedAbstractMethod",
792 IssueKind::UnimplementedInterfaceMethod { .. } => "UnimplementedInterfaceMethod",
793 IssueKind::MethodSignatureMismatch { .. } => "MethodSignatureMismatch",
794 IssueKind::OverriddenMethodAccess { .. } => "OverriddenMethodAccess",
795 IssueKind::FinalClassExtended { .. } => "FinalClassExtended",
796 IssueKind::FinalMethodOverridden { .. } => "FinalMethodOverridden",
797 IssueKind::AbstractInstantiation { .. } => "AbstractInstantiation",
798 IssueKind::InterfaceInstantiation { .. } => "InterfaceInstantiation",
799 IssueKind::InvalidOverride { .. } => "InvalidOverride",
800 IssueKind::ReadonlyPropertyAssignment { .. } => "ReadonlyPropertyAssignment",
801 IssueKind::InvalidTemplateParam { .. } => "InvalidTemplateParam",
802 IssueKind::ShadowedTemplateParam { .. } => "ShadowedTemplateParam",
803 IssueKind::TaintedInput { .. } => "TaintedInput",
804 IssueKind::TaintedHtml => "TaintedHtml",
805 IssueKind::TaintedSql => "TaintedSql",
806 IssueKind::TaintedShell => "TaintedShell",
807 IssueKind::DeprecatedCall { .. } => "DeprecatedCall",
808 IssueKind::DeprecatedProperty { .. } => "DeprecatedProperty",
809 IssueKind::DeprecatedConstant { .. } => "DeprecatedConstant",
810 IssueKind::DeprecatedInterface { .. } => "DeprecatedInterface",
811 IssueKind::DeprecatedTrait { .. } => "DeprecatedTrait",
812 IssueKind::DeprecatedMethodCall { .. } => "DeprecatedMethodCall",
813 IssueKind::DeprecatedMethod { .. } => "DeprecatedMethod",
814 IssueKind::DeprecatedClass { .. } => "DeprecatedClass",
815 IssueKind::InternalMethod { .. } => "InternalMethod",
816 IssueKind::MissingReturnType { .. } => "MissingReturnType",
817 IssueKind::MissingParamType { .. } => "MissingParamType",
818 IssueKind::InvalidThrow { .. } => "InvalidThrow",
819 IssueKind::InvalidCatch { .. } => "InvalidCatch",
820 IssueKind::MissingThrowsDocblock { .. } => "MissingThrowsDocblock",
821 IssueKind::ImplicitToStringCast { .. } => "ImplicitToStringCast",
822 IssueKind::ImplicitFloatToIntCast { .. } => "ImplicitFloatToIntCast",
823 IssueKind::ParseError { .. } => "ParseError",
824 IssueKind::InvalidDocblock { .. } => "InvalidDocblock",
825 IssueKind::MixedArgument { .. } => "MixedArgument",
826 IssueKind::MixedAssignment { .. } => "MixedAssignment",
827 IssueKind::MixedMethodCall { .. } => "MixedMethodCall",
828 IssueKind::MixedPropertyFetch { .. } => "MixedPropertyFetch",
829 IssueKind::MixedClone => "MixedClone",
830 IssueKind::InvalidClone { .. } => "InvalidClone",
831 IssueKind::PossiblyInvalidClone { .. } => "PossiblyInvalidClone",
832 IssueKind::InvalidToString { .. } => "InvalidToString",
833 IssueKind::CircularInheritance { .. } => "CircularInheritance",
834 IssueKind::InvalidTraitUse { .. } => "InvalidTraitUse",
835 }
836 }
837
838 pub fn message(&self) -> String {
840 match self {
841 IssueKind::NonStaticSelfCall { class, method } => {
842 format!("Non-static method {class}::{method}() cannot be called statically")
843 }
844 IssueKind::DirectConstructorCall { class } => {
845 format!("Cannot call constructor of {class} directly")
846 }
847 IssueKind::InvalidScope { in_class } => {
848 if *in_class {
849 "$this cannot be used in a static method".to_string()
850 } else {
851 "$this cannot be used outside of a class".to_string()
852 }
853 }
854 IssueKind::UndefinedVariable { name } => format!("Variable ${name} is not defined"),
855 IssueKind::UndefinedFunction { name } => format!("Function {name}() is not defined"),
856 IssueKind::UndefinedMethod { class, method } => {
857 format!("Method {class}::{method}() does not exist")
858 }
859 IssueKind::UndefinedClass { name } => format!("Class {name} does not exist"),
860 IssueKind::UndefinedProperty { class, property } => {
861 format!("Property {class}::${property} does not exist")
862 }
863 IssueKind::UndefinedConstant { name } => format!("Constant {name} is not defined"),
864 IssueKind::PossiblyUndefinedVariable { name } => {
865 format!("Variable ${name} might not be defined")
866 }
867 IssueKind::UndefinedTrait { name } => format!("Trait {name} does not exist"),
868 IssueKind::InvalidStringClass { actual } => {
869 format!("Dynamic class instantiation requires string or class-string type, got '{actual}'")
870 }
871
872 IssueKind::NullArgument { param, fn_name } => {
873 format!("Argument ${param} of {fn_name}() cannot be null")
874 }
875 IssueKind::NullPropertyFetch { property } => {
876 format!("Cannot access property ${property} on null")
877 }
878 IssueKind::NullMethodCall { method } => {
879 format!("Cannot call method {method}() on null")
880 }
881 IssueKind::NullArrayAccess => "Cannot access array on null".to_string(),
882 IssueKind::PossiblyNullArgument { param, fn_name } => {
883 format!("Argument ${param} of {fn_name}() might be null")
884 }
885 IssueKind::PossiblyInvalidArgument {
886 param,
887 fn_name,
888 expected,
889 actual,
890 } => {
891 format!("Argument ${param} of {fn_name}() expects '{expected}', possibly different type '{actual}' provided")
892 }
893 IssueKind::PossiblyNullPropertyFetch { property } => {
894 format!("Cannot access property ${property} on possibly null value")
895 }
896 IssueKind::PossiblyNullMethodCall { method } => {
897 format!("Cannot call method {method}() on possibly null value")
898 }
899 IssueKind::PossiblyNullArrayAccess => {
900 "Cannot access array on possibly null value".to_string()
901 }
902 IssueKind::NullableReturnStatement { expected, actual } => {
903 format!("Return type '{actual}' is not compatible with declared '{expected}'")
904 }
905
906 IssueKind::InvalidReturnType { expected, actual } => {
907 format!("Return type '{actual}' is not compatible with declared '{expected}'")
908 }
909 IssueKind::InvalidArgument {
910 param,
911 fn_name,
912 expected,
913 actual,
914 } => {
915 format!("Argument ${param} of {fn_name}() expects '{expected}', got '{actual}'")
916 }
917 IssueKind::TooFewArguments {
918 fn_name,
919 expected,
920 actual,
921 } => {
922 format!(
923 "Too few arguments for {}(): expected {}, got {}",
924 fn_name, expected, actual
925 )
926 }
927 IssueKind::TooManyArguments {
928 fn_name,
929 expected,
930 actual,
931 } => {
932 format!(
933 "Too many arguments for {}(): expected {}, got {}",
934 fn_name, expected, actual
935 )
936 }
937 IssueKind::InvalidNamedArgument { fn_name, name } => {
938 format!("{}() has no parameter named ${}", fn_name, name)
939 }
940 IssueKind::InvalidPassByReference { fn_name, param } => {
941 format!(
942 "Argument ${} of {}() must be passed by reference",
943 param, fn_name
944 )
945 }
946 IssueKind::InvalidPropertyFetch { ty } => {
947 format!("Cannot fetch property on non-object type '{ty}'")
948 }
949 IssueKind::InvalidArrayAccess { ty } => {
950 format!("Cannot use [] operator on non-array type '{ty}'")
951 }
952 IssueKind::InvalidArrayAssignment { ty } => {
953 format!("Cannot use [] assignment on non-array type '{ty}'")
954 }
955 IssueKind::InvalidPropertyAssignment {
956 property,
957 expected,
958 actual,
959 } => {
960 format!("Property ${property} expects '{expected}', cannot assign '{actual}'")
961 }
962 IssueKind::InvalidCast { from, to } => {
963 format!("Cannot cast '{from}' to '{to}'")
964 }
965 IssueKind::InvalidStaticInvocation { class, method } => {
966 format!("Non-static method {class}::{method}() cannot be called statically")
967 }
968 IssueKind::InvalidOperand { op, left, right } => {
969 format!("Operator '{op}' not supported between '{left}' and '{right}'")
970 }
971 IssueKind::PossiblyInvalidOperand { op, left, right } => {
972 format!("Operator '{op}' might not be supported between '{left}' and '{right}'")
973 }
974 IssueKind::PossiblyNullOperand { op, ty } => {
975 format!("Operator '{op}' operand '{ty}' might be null")
976 }
977 IssueKind::MismatchingDocblockReturnType { declared, inferred } => {
978 format!("Docblock return type '{declared}' does not match inferred '{inferred}'")
979 }
980 IssueKind::MismatchingDocblockParamType {
981 param,
982 declared,
983 inferred,
984 } => {
985 format!(
986 "Docblock type '{declared}' for ${param} does not match inferred '{inferred}'"
987 )
988 }
989 IssueKind::TypeCheckMismatch {
990 var,
991 expected,
992 actual,
993 } => {
994 format!("Type of ${var} is expected to be {expected}, got {actual}")
995 }
996
997 IssueKind::InvalidArrayOffset { expected, actual } => {
998 format!("Array offset expects '{expected}', got '{actual}'")
999 }
1000 IssueKind::NonExistentArrayOffset { key } => {
1001 format!("Array offset '{key}' does not exist")
1002 }
1003 IssueKind::PossiblyInvalidArrayOffset { expected, actual } => {
1004 format!("Array offset might be invalid: expects '{expected}', got '{actual}'")
1005 }
1006
1007 IssueKind::RedundantCondition { ty } => {
1008 format!("Condition is always true/false for type '{ty}'")
1009 }
1010 IssueKind::RedundantCast { from, to } => {
1011 format!("Casting '{from}' to '{to}' is redundant")
1012 }
1013 IssueKind::UnnecessaryVarAnnotation { var } => {
1014 format!("@var annotation for ${var} is unnecessary")
1015 }
1016 IssueKind::TypeDoesNotContainType { left, right } => {
1017 format!("Type '{left}' can never contain type '{right}'")
1018 }
1019 IssueKind::ParadoxicalCondition { value } => {
1020 format!("Value {value} is duplicated; this branch can never be reached")
1021 }
1022
1023 IssueKind::UnusedVariable { name } => format!("Variable ${name} is never read"),
1024 IssueKind::UnusedParam { name } => format!("Parameter ${name} is never used"),
1025 IssueKind::UnreachableCode => "Unreachable code detected".to_string(),
1026 IssueKind::UnusedMethod { class, method } => {
1027 format!("Private method {class}::{method}() is never called")
1028 }
1029 IssueKind::UnusedProperty { class, property } => {
1030 format!("Private property {class}::${property} is never read")
1031 }
1032 IssueKind::UnusedFunction { name } => {
1033 format!("Function {name}() is never called")
1034 }
1035 IssueKind::UnusedForeachValue { name } => {
1036 format!("Foreach value ${name} is never read")
1037 }
1038
1039 IssueKind::UnimplementedAbstractMethod { class, method } => {
1040 format!("Class {class} must implement abstract method {method}()")
1041 }
1042 IssueKind::UnimplementedInterfaceMethod {
1043 class,
1044 interface,
1045 method,
1046 } => {
1047 format!("Class {class} must implement {interface}::{method}() from interface")
1048 }
1049 IssueKind::MethodSignatureMismatch {
1050 class,
1051 method,
1052 detail,
1053 } => {
1054 format!("Method {class}::{method}() signature mismatch: {detail}")
1055 }
1056 IssueKind::OverriddenMethodAccess { class, method } => {
1057 format!("Method {class}::{method}() overrides with less visibility")
1058 }
1059 IssueKind::ReadonlyPropertyAssignment { class, property } => {
1060 format!(
1061 "Cannot assign to readonly property {class}::${property} outside of constructor"
1062 )
1063 }
1064 IssueKind::FinalClassExtended { parent, child } => {
1065 format!("Class {child} cannot extend final class {parent}")
1066 }
1067 IssueKind::InvalidTemplateParam {
1068 name,
1069 expected_bound,
1070 actual,
1071 } => {
1072 format!(
1073 "Template type '{name}' inferred as '{actual}' does not satisfy bound '{expected_bound}'"
1074 )
1075 }
1076 IssueKind::ShadowedTemplateParam { name } => {
1077 format!(
1078 "Method template parameter '{name}' shadows class-level template parameter with the same name"
1079 )
1080 }
1081 IssueKind::FinalMethodOverridden {
1082 class,
1083 method,
1084 parent,
1085 } => {
1086 format!("Method {class}::{method}() cannot override final method from {parent}")
1087 }
1088 IssueKind::AbstractInstantiation { class } => {
1089 format!("Cannot instantiate abstract class {class}")
1090 }
1091 IssueKind::InterfaceInstantiation { class } => {
1092 format!("Cannot instantiate interface {class}")
1093 }
1094 IssueKind::InvalidOverride {
1095 class,
1096 method,
1097 detail,
1098 } => {
1099 format!("Method {class}::{method}() has #[Override] but {detail}")
1100 }
1101
1102 IssueKind::TaintedInput { sink } => format!("Tainted input reaching sink '{sink}'"),
1103 IssueKind::TaintedHtml => "Tainted HTML output — possible XSS".to_string(),
1104 IssueKind::TaintedSql => "Tainted SQL query — possible SQL injection".to_string(),
1105 IssueKind::TaintedShell => {
1106 "Tainted shell command — possible command injection".to_string()
1107 }
1108
1109 IssueKind::DeprecatedCall { name, message } => {
1110 let base = format!("Call to deprecated function {name}");
1111 append_deprecation_message(base, message)
1112 }
1113 IssueKind::DeprecatedProperty {
1114 class,
1115 property,
1116 message,
1117 } => {
1118 let base = format!("Property {class}::${property} is deprecated");
1119 append_deprecation_message(base, message)
1120 }
1121 IssueKind::DeprecatedConstant {
1122 class,
1123 constant,
1124 message,
1125 } => {
1126 let base = format!("Constant {class}::{constant} is deprecated");
1127 append_deprecation_message(base, message)
1128 }
1129 IssueKind::DeprecatedInterface { name, message } => {
1130 let base = format!("Interface {name} is deprecated");
1131 append_deprecation_message(base, message)
1132 }
1133 IssueKind::DeprecatedTrait { name, message } => {
1134 let base = format!("Trait {name} is deprecated");
1135 append_deprecation_message(base, message)
1136 }
1137 IssueKind::DeprecatedMethodCall {
1138 class,
1139 method,
1140 message,
1141 } => {
1142 let base = format!("Call to deprecated method {class}::{method}");
1143 append_deprecation_message(base, message)
1144 }
1145 IssueKind::DeprecatedMethod {
1146 class,
1147 method,
1148 message,
1149 } => {
1150 let base = format!("Method {class}::{method}() is deprecated");
1151 append_deprecation_message(base, message)
1152 }
1153 IssueKind::DeprecatedClass { name, message } => {
1154 let base = format!("Class {name} is deprecated");
1155 append_deprecation_message(base, message)
1156 }
1157 IssueKind::InternalMethod { class, method } => {
1158 format!("Method {class}::{method}() is marked @internal")
1159 }
1160 IssueKind::MissingReturnType { fn_name } => {
1161 format!("Function {fn_name}() has no return type annotation")
1162 }
1163 IssueKind::MissingParamType { fn_name, param } => {
1164 format!("Parameter ${param} of {fn_name}() has no type annotation")
1165 }
1166 IssueKind::InvalidThrow { ty } => {
1167 format!("Thrown type '{ty}' does not extend Throwable")
1168 }
1169 IssueKind::InvalidCatch { ty } => {
1170 format!("Caught type '{ty}' does not extend Throwable")
1171 }
1172 IssueKind::MissingThrowsDocblock { class } => {
1173 format!("Exception {class} is thrown but not declared in @throws")
1174 }
1175 IssueKind::ImplicitToStringCast { class } => {
1176 format!("Class {class} is implicitly cast to string")
1177 }
1178 IssueKind::ImplicitFloatToIntCast { from } => {
1179 format!("Implicit cast from {from} to int truncates the fractional part")
1180 }
1181 IssueKind::ParseError { message } => format!("Parse error: {message}"),
1182 IssueKind::InvalidDocblock { message } => format!("Invalid docblock: {message}"),
1183 IssueKind::MixedArgument { param, fn_name } => {
1184 format!("Argument ${param} of {fn_name}() is mixed")
1185 }
1186 IssueKind::MixedAssignment { var } => {
1187 format!("Variable ${var} is assigned a mixed type")
1188 }
1189 IssueKind::MixedMethodCall { method } => {
1190 format!("Method {method}() called on mixed type")
1191 }
1192 IssueKind::MixedPropertyFetch { property } => {
1193 format!("Property ${property} fetched on mixed type")
1194 }
1195 IssueKind::MixedClone => "cannot clone mixed".to_string(),
1196 IssueKind::InvalidClone { ty } => format!("cannot clone non-object {ty}"),
1197 IssueKind::PossiblyInvalidClone { ty } => {
1198 format!("cannot clone possibly non-object {ty}")
1199 }
1200 IssueKind::InvalidToString { class } => {
1201 format!("Method {class}::__toString() must return a string")
1202 }
1203 IssueKind::CircularInheritance { class } => {
1204 format!("Class {class} has a circular inheritance chain")
1205 }
1206 IssueKind::InvalidTraitUse { trait_name, reason } => {
1207 format!("Trait {trait_name} used incorrectly: {reason}")
1208 }
1209 }
1210 }
1211}
1212
1213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1218pub struct Issue {
1219 pub kind: IssueKind,
1220 pub severity: Severity,
1221 pub location: Location,
1222 pub snippet: Option<String>,
1223 pub suppressed: bool,
1224}
1225
1226impl Issue {
1227 pub fn new(kind: IssueKind, location: Location) -> Self {
1228 let severity = kind.default_severity();
1229 Self {
1230 severity,
1231 kind,
1232 location,
1233 snippet: None,
1234 suppressed: false,
1235 }
1236 }
1237
1238 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
1239 self.snippet = Some(snippet.into());
1240 self
1241 }
1242
1243 pub fn suppress(mut self) -> Self {
1244 self.suppressed = true;
1245 self
1246 }
1247}
1248
1249impl fmt::Display for Issue {
1250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1251 let sev = match self.severity {
1252 Severity::Error => "error".red().to_string(),
1253 Severity::Warning => "warning".yellow().to_string(),
1254 Severity::Info => "info".blue().to_string(),
1255 };
1256 write!(
1257 f,
1258 "{} {}[{}] {}: {}",
1259 self.location.bright_black(),
1260 sev,
1261 self.kind.code().bright_black(),
1262 self.kind.name().bold(),
1263 self.kind.message()
1264 )
1265 }
1266}
1267
1268#[derive(Debug, Default)]
1273pub struct IssueBuffer {
1274 issues: Vec<Issue>,
1275 seen: HashSet<(&'static str, Arc<str>, u32, u16)>,
1276 file_suppressions: Vec<String>,
1278}
1279
1280impl IssueBuffer {
1281 pub fn new() -> Self {
1282 Self::default()
1283 }
1284
1285 pub fn add(&mut self, issue: Issue) {
1286 let key = (
1287 issue.kind.name(),
1288 issue.location.file.clone(),
1289 issue.location.line,
1290 issue.location.col_start,
1291 );
1292 if self.seen.insert(key) {
1293 self.issues.push(issue);
1294 }
1295 }
1296
1297 pub fn add_suppression(&mut self, name: impl Into<String>) {
1298 self.file_suppressions.push(name.into());
1299 }
1300
1301 pub fn into_issues(self) -> Vec<Issue> {
1303 self.issues
1304 .into_iter()
1305 .filter(|i| !i.suppressed)
1306 .filter(|i| !self.file_suppressions.contains(&i.kind.name().to_string()))
1307 .collect()
1308 }
1309
1310 pub fn suppress_range(&mut self, from: usize, suppressions: &[String]) {
1313 if suppressions.is_empty() {
1314 return;
1315 }
1316 for issue in self.issues[from..].iter_mut() {
1317 if suppressions.iter().any(|s| s == issue.kind.name()) {
1318 issue.suppressed = true;
1319 }
1320 }
1321 }
1322
1323 pub fn issue_count(&self) -> usize {
1326 self.issues.len()
1327 }
1328
1329 pub fn is_empty(&self) -> bool {
1330 self.issues.is_empty()
1331 }
1332
1333 pub fn len(&self) -> usize {
1334 self.issues.len()
1335 }
1336
1337 pub fn error_count(&self) -> usize {
1338 self.issues
1339 .iter()
1340 .filter(|i| !i.suppressed && i.severity == Severity::Error)
1341 .count()
1342 }
1343
1344 pub fn warning_count(&self) -> usize {
1345 self.issues
1346 .iter()
1347 .filter(|i| !i.suppressed && i.severity == Severity::Warning)
1348 .count()
1349 }
1350}
1351
1352#[cfg(test)]
1353mod code_tests {
1354 use super::*;
1355 use std::collections::HashSet;
1356
1357 fn one_of_each() -> Vec<IssueKind> {
1363 let s = || String::new();
1364 vec![
1365 IssueKind::InvalidScope { in_class: false },
1366 IssueKind::NonStaticSelfCall {
1367 class: s(),
1368 method: s(),
1369 },
1370 IssueKind::DirectConstructorCall { class: s() },
1371 IssueKind::UndefinedVariable { name: s() },
1372 IssueKind::UndefinedFunction { name: s() },
1373 IssueKind::UndefinedMethod {
1374 class: s(),
1375 method: s(),
1376 },
1377 IssueKind::UndefinedClass { name: s() },
1378 IssueKind::UndefinedProperty {
1379 class: s(),
1380 property: s(),
1381 },
1382 IssueKind::UndefinedConstant { name: s() },
1383 IssueKind::PossiblyUndefinedVariable { name: s() },
1384 IssueKind::NullArgument {
1385 param: s(),
1386 fn_name: s(),
1387 },
1388 IssueKind::NullPropertyFetch { property: s() },
1389 IssueKind::NullMethodCall { method: s() },
1390 IssueKind::NullArrayAccess,
1391 IssueKind::PossiblyNullArgument {
1392 param: s(),
1393 fn_name: s(),
1394 },
1395 IssueKind::PossiblyInvalidArgument {
1396 param: s(),
1397 fn_name: s(),
1398 expected: s(),
1399 actual: s(),
1400 },
1401 IssueKind::PossiblyNullPropertyFetch { property: s() },
1402 IssueKind::PossiblyNullMethodCall { method: s() },
1403 IssueKind::PossiblyNullArrayAccess,
1404 IssueKind::NullableReturnStatement {
1405 expected: s(),
1406 actual: s(),
1407 },
1408 IssueKind::InvalidReturnType {
1409 expected: s(),
1410 actual: s(),
1411 },
1412 IssueKind::InvalidArgument {
1413 param: s(),
1414 fn_name: s(),
1415 expected: s(),
1416 actual: s(),
1417 },
1418 IssueKind::TooFewArguments {
1419 fn_name: s(),
1420 expected: 0,
1421 actual: 0,
1422 },
1423 IssueKind::TooManyArguments {
1424 fn_name: s(),
1425 expected: 0,
1426 actual: 0,
1427 },
1428 IssueKind::InvalidNamedArgument {
1429 fn_name: s(),
1430 name: s(),
1431 },
1432 IssueKind::InvalidPassByReference {
1433 fn_name: s(),
1434 param: s(),
1435 },
1436 IssueKind::InvalidPropertyFetch { ty: s() },
1437 IssueKind::InvalidArrayAccess { ty: s() },
1438 IssueKind::InvalidArrayAssignment { ty: s() },
1439 IssueKind::InvalidPropertyAssignment {
1440 property: s(),
1441 expected: s(),
1442 actual: s(),
1443 },
1444 IssueKind::InvalidCast { from: s(), to: s() },
1445 IssueKind::InvalidStaticInvocation {
1446 class: s(),
1447 method: s(),
1448 },
1449 IssueKind::InvalidOperand {
1450 op: s(),
1451 left: s(),
1452 right: s(),
1453 },
1454 IssueKind::PossiblyInvalidOperand {
1455 op: s(),
1456 left: s(),
1457 right: s(),
1458 },
1459 IssueKind::PossiblyNullOperand { op: s(), ty: s() },
1460 IssueKind::MismatchingDocblockReturnType {
1461 declared: s(),
1462 inferred: s(),
1463 },
1464 IssueKind::MismatchingDocblockParamType {
1465 param: s(),
1466 declared: s(),
1467 inferred: s(),
1468 },
1469 IssueKind::TypeCheckMismatch {
1470 var: s(),
1471 expected: s(),
1472 actual: s(),
1473 },
1474 IssueKind::InvalidArrayOffset {
1475 expected: s(),
1476 actual: s(),
1477 },
1478 IssueKind::NonExistentArrayOffset { key: s() },
1479 IssueKind::PossiblyInvalidArrayOffset {
1480 expected: s(),
1481 actual: s(),
1482 },
1483 IssueKind::RedundantCondition { ty: s() },
1484 IssueKind::RedundantCast { from: s(), to: s() },
1485 IssueKind::UnnecessaryVarAnnotation { var: s() },
1486 IssueKind::TypeDoesNotContainType {
1487 left: s(),
1488 right: s(),
1489 },
1490 IssueKind::UnusedVariable { name: s() },
1491 IssueKind::UnusedParam { name: s() },
1492 IssueKind::UnreachableCode,
1493 IssueKind::UnusedMethod {
1494 class: s(),
1495 method: s(),
1496 },
1497 IssueKind::UnusedProperty {
1498 class: s(),
1499 property: s(),
1500 },
1501 IssueKind::UnusedFunction { name: s() },
1502 IssueKind::UnusedForeachValue { name: s() },
1503 IssueKind::ReadonlyPropertyAssignment {
1504 class: s(),
1505 property: s(),
1506 },
1507 IssueKind::UnimplementedAbstractMethod {
1508 class: s(),
1509 method: s(),
1510 },
1511 IssueKind::UnimplementedInterfaceMethod {
1512 class: s(),
1513 interface: s(),
1514 method: s(),
1515 },
1516 IssueKind::MethodSignatureMismatch {
1517 class: s(),
1518 method: s(),
1519 detail: s(),
1520 },
1521 IssueKind::OverriddenMethodAccess {
1522 class: s(),
1523 method: s(),
1524 },
1525 IssueKind::FinalClassExtended {
1526 parent: s(),
1527 child: s(),
1528 },
1529 IssueKind::FinalMethodOverridden {
1530 class: s(),
1531 method: s(),
1532 parent: s(),
1533 },
1534 IssueKind::AbstractInstantiation { class: s() },
1535 IssueKind::InterfaceInstantiation { class: s() },
1536 IssueKind::InvalidOverride {
1537 class: s(),
1538 method: s(),
1539 detail: s(),
1540 },
1541 IssueKind::CircularInheritance { class: s() },
1542 IssueKind::TaintedInput { sink: s() },
1543 IssueKind::TaintedHtml,
1544 IssueKind::TaintedSql,
1545 IssueKind::TaintedShell,
1546 IssueKind::InvalidTemplateParam {
1547 name: s(),
1548 expected_bound: s(),
1549 actual: s(),
1550 },
1551 IssueKind::ShadowedTemplateParam { name: s() },
1552 IssueKind::DeprecatedCall {
1553 name: s(),
1554 message: None,
1555 },
1556 IssueKind::DeprecatedProperty {
1557 class: s(),
1558 property: s(),
1559 message: None,
1560 },
1561 IssueKind::DeprecatedConstant {
1562 class: s(),
1563 constant: s(),
1564 message: None,
1565 },
1566 IssueKind::DeprecatedInterface {
1567 name: s(),
1568 message: None,
1569 },
1570 IssueKind::DeprecatedTrait {
1571 name: s(),
1572 message: None,
1573 },
1574 IssueKind::DeprecatedMethodCall {
1575 class: s(),
1576 method: s(),
1577 message: None,
1578 },
1579 IssueKind::DeprecatedMethod {
1580 class: s(),
1581 method: s(),
1582 message: None,
1583 },
1584 IssueKind::DeprecatedClass {
1585 name: s(),
1586 message: None,
1587 },
1588 IssueKind::InternalMethod {
1589 class: s(),
1590 method: s(),
1591 },
1592 IssueKind::MissingReturnType { fn_name: s() },
1593 IssueKind::MissingParamType {
1594 fn_name: s(),
1595 param: s(),
1596 },
1597 IssueKind::MissingThrowsDocblock { class: s() },
1598 IssueKind::InvalidDocblock { message: s() },
1599 IssueKind::MixedArgument {
1600 param: s(),
1601 fn_name: s(),
1602 },
1603 IssueKind::MixedAssignment { var: s() },
1604 IssueKind::MixedMethodCall { method: s() },
1605 IssueKind::MixedPropertyFetch { property: s() },
1606 IssueKind::MixedClone,
1607 IssueKind::InvalidClone { ty: s() },
1608 IssueKind::PossiblyInvalidClone { ty: s() },
1609 IssueKind::InvalidToString { class: s() },
1610 IssueKind::InvalidTraitUse {
1611 trait_name: s(),
1612 reason: s(),
1613 },
1614 IssueKind::ParseError { message: s() },
1615 IssueKind::InvalidThrow { ty: s() },
1616 IssueKind::InvalidCatch { ty: s() },
1617 IssueKind::ImplicitToStringCast { class: s() },
1618 IssueKind::ImplicitFloatToIntCast { from: s() },
1619 ]
1620 }
1621
1622 #[test]
1623 fn codes_have_expected_shape() {
1624 for kind in one_of_each() {
1625 let code = kind.code();
1626 assert!(
1627 code.len() == 7
1628 && code.starts_with("MIR")
1629 && code[3..].chars().all(|c| c.is_ascii_digit()),
1630 "code {code:?} for {} does not match MIR####",
1631 kind.name(),
1632 );
1633 }
1634 }
1635
1636 #[test]
1637 fn codes_are_unique() {
1638 let kinds = one_of_each();
1639 let mut seen: HashSet<&'static str> = HashSet::new();
1640 for kind in &kinds {
1641 assert!(
1642 seen.insert(kind.code()),
1643 "duplicate code {} (variant {})",
1644 kind.code(),
1645 kind.name(),
1646 );
1647 }
1648 }
1649
1650 #[test]
1651 fn display_includes_code() {
1652 let issue = Issue::new(
1653 IssueKind::UndefinedClass {
1654 name: "Foo".to_string(),
1655 },
1656 Location {
1657 file: Arc::from("src/x.php"),
1658 line: 1,
1659 line_end: 1,
1660 col_start: 0,
1661 col_end: 3,
1662 },
1663 );
1664 let raw = format!("{issue}");
1667 let stripped: String = {
1668 let mut out = String::new();
1669 let mut chars = raw.chars();
1670 while let Some(c) = chars.next() {
1671 if c == '\u{1b}' {
1672 for c2 in chars.by_ref() {
1673 if c2 == 'm' {
1674 break;
1675 }
1676 }
1677 } else {
1678 out.push(c);
1679 }
1680 }
1681 out
1682 };
1683 assert!(
1684 stripped.contains("error[MIR0005] UndefinedClass:"),
1685 "Display output missing code/name segment: {stripped:?}",
1686 );
1687 }
1688
1689 #[test]
1692 fn one_of_each_has_every_variant() {
1693 assert_eq!(one_of_each().len(), 96);
1697 }
1698}