1use crate::PhpVersion;
2use php_ast::Span;
3use std::borrow::Cow;
4use thiserror::Error;
5
6#[derive(Debug, Clone, Error)]
12#[non_exhaustive]
13pub enum ParseError {
14 #[error("expected {expected}, found {found}")]
16 #[non_exhaustive]
17 Expected {
18 expected: Cow<'static, str>,
20 found: Cow<'static, str>,
24 span: Span,
26 },
27
28 #[error("unterminated string literal")]
30 #[non_exhaustive]
31 UnterminatedString {
32 span: Span,
34 },
35
36 #[error("expected {expected} after {after}")]
38 #[non_exhaustive]
39 ExpectedAfter {
40 expected: Cow<'static, str>,
42 after: Cow<'static, str>,
44 span: Span,
46 },
47
48 #[error("missing closing {delimiter}")]
50 #[non_exhaustive]
51 UnclosedDelimiter {
52 delimiter: Cow<'static, str>,
54 opened_at: Span,
56 span: Span,
58 },
59
60 #[error("unmatched {delimiter}")]
62 #[non_exhaustive]
63 UnmatchedDelimiter {
64 delimiter: Cow<'static, str>,
66 span: Span,
68 },
69
70 #[error("{message}")]
73 #[non_exhaustive]
74 Forbidden {
75 rule: ForbiddenRule,
77 message: Cow<'static, str>,
79 span: Span,
81 },
82
83 #[error("maximum nesting depth exceeded")]
87 #[non_exhaustive]
88 NestingTooDeep {
89 span: Span,
91 },
92
93 #[error("source is {len} bytes; the maximum is 4 GiB")]
96 #[non_exhaustive]
97 SourceTooLarge {
98 len: usize,
100 span: Span,
102 },
103
104 #[error("'{feature}' requires PHP {required} or higher")]
108 #[non_exhaustive]
109 VersionTooLow {
110 feature: Cow<'static, str>,
112 required: PhpVersion,
114 used: PhpVersion,
116 span: Span,
118 },
119}
120
121impl ParseError {
122 pub fn span(&self) -> Span {
124 match self {
125 ParseError::Expected { span, .. }
126 | ParseError::UnterminatedString { span }
127 | ParseError::ExpectedAfter { span, .. }
128 | ParseError::UnclosedDelimiter { span, .. }
129 | ParseError::UnmatchedDelimiter { span, .. }
130 | ParseError::Forbidden { span, .. }
131 | ParseError::NestingTooDeep { span }
132 | ParseError::SourceTooLarge { span, .. }
133 | ParseError::VersionTooLow { span, .. } => *span,
134 }
135 }
136
137 pub fn forbidden_rule(&self) -> Option<ForbiddenRule> {
139 match self {
140 ParseError::Forbidden { rule, .. } => Some(*rule),
141 _ => None,
142 }
143 }
144
145 pub fn phase(&self) -> Phase {
147 self.forbidden_rule()
148 .map_or(Phase::Parse, ForbiddenRule::phase)
149 }
150
151 pub fn code(&self) -> &'static str {
153 match self {
154 ParseError::Expected { .. } => "expected",
155 ParseError::UnterminatedString { .. } => "unterminated-string",
156 ParseError::ExpectedAfter { .. } => "expected-after",
157 ParseError::UnclosedDelimiter { .. } => "unclosed-delimiter",
158 ParseError::UnmatchedDelimiter { .. } => "unmatched-delimiter",
159 ParseError::Forbidden { rule, .. } => rule.as_str(),
160 ParseError::NestingTooDeep { .. } => "nesting-too-deep",
161 ParseError::SourceTooLarge { .. } => "source-too-large",
162 ParseError::VersionTooLow { .. } => "version-too-low",
163 }
164 }
165
166 pub fn leaves_ast_incomplete(&self) -> bool {
168 !matches!(
169 self,
170 ParseError::Forbidden { .. } | ParseError::VersionTooLow { .. }
171 )
172 }
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
177pub enum Phase {
178 Parse,
180 Compile,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Error)]
186#[error("unknown forbidden rule")]
187pub struct UnknownForbiddenRule;
188
189macro_rules! forbidden_rules {
190 ($($(#[$doc:meta])* $rule:ident = $id:literal, $phase:ident;)*) => {
191 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
195 #[non_exhaustive]
196 pub enum ForbiddenRule {
197 $($(#[$doc])* $rule,)*
198 }
199
200 impl ForbiddenRule {
201 pub const ALL: &'static [ForbiddenRule] = &[$(ForbiddenRule::$rule),*];
203
204 pub fn as_str(self) -> &'static str {
206 match self {
207 $(ForbiddenRule::$rule => $id,)*
208 }
209 }
210
211 pub fn phase(self) -> Phase {
213 match self {
214 $(ForbiddenRule::$rule => Phase::$phase,)*
215 }
216 }
217 }
218 };
219}
220
221forbidden_rules! {
222 ClassKeywordOutsideClass = "class-keyword-outside-class", Compile;
224 ParentWithoutParentClass = "parent-without-parent-class", Compile;
226 ToStringReturnType = "to-string-return-type", Compile;
228 MagicMethodReturnType = "magic-method-return-type", Compile;
230 AttributeTargetMismatch = "attribute-target-mismatch", Compile;
232 AttributeOnInvalidClass = "attribute-on-invalid-class", Compile;
234 DeprecatedOnInvalidTarget = "deprecated-on-invalid-target", Compile;
236 AllowDynamicPropertiesOnInvalidClass = "allow-dynamic-properties-on-invalid-class", Compile;
238 MissingReturnValue = "missing-return-value", Compile;
240 GeneratorReturnType = "generator-return-type", Compile;
242 ReadonlyPropertyDefault = "readonly-property-default", Compile;
244 DuplicateParameter = "duplicate-parameter", Compile;
246 ReassignThis = "reassign-this", Compile;
248 UnclosedInterpolation = "unclosed-interpolation", Parse;
250 EmptyInterpolationIndex = "empty-interpolation-index", Parse;
252 EmptyInterpolationVariableName = "empty-interpolation-variable-name", Parse;
254 UnclosedInterpolationIndex = "unclosed-interpolation-index", Parse;
256 ClassConstantInterpolation = "class-constant-interpolation", Parse;
258 InvalidInterpolationOffset = "invalid-interpolation-offset", Parse;
260 InvalidNumericLiteral = "invalid-numeric-literal", Parse;
262 InvalidCodepointEscape = "invalid-codepoint-escape", Parse;
264 MixedHeredocIndentation = "mixed-heredoc-indentation", Parse;
266 HeredocBodyIndentation = "heredoc-body-indentation", Parse;
268 WhitespaceInNamespacedName = "whitespace-in-namespaced-name", Parse;
270 MultipleVisibilityModifiers = "multiple-visibility-modifiers", Parse;
272 MultipleSetVisibilityModifiers = "multiple-set-visibility-modifiers", Parse;
274 DuplicateModifier = "duplicate-modifier", Parse;
276 AbstractFinalConflict = "abstract-final-conflict", Parse;
278 InvalidConstantModifier = "invalid-constant-modifier", Parse;
280 ModifierNotAllowed = "modifier-not-allowed", Parse;
282 KeywordAsDeclarationName = "keyword-as-declaration-name", Parse;
284 CustomAssertFunction = "custom-assert-function", Compile;
286 ReservedClassName = "reserved-class-name", Parse;
288 OutermostScopeOnly = "outermost-scope-only", Parse;
290 UnparenthesizedIntersectionInUnion = "unparenthesized-intersection-in-union", Parse;
292 ParenthesizedIntersectionOutsideUnion = "parenthesized-intersection-outside-union", Parse;
294 StaticTypePosition = "static-type-position", Parse;
296 EmptyAttributeList = "empty-attribute-list", Parse;
298 CallTimePassByReference = "call-time-pass-by-reference", Parse;
300 CurlyOffsetAccess = "curly-offset-access", Parse;
302 VoidCastInExpression = "void-cast-in-expression", Parse;
304 RealCast = "real-cast", Parse;
306 NonAssociativeChain = "non-associative-chain", Parse;
308 IncDecAsAssignTarget = "inc-dec-as-assign-target", Parse;
310 InvalidAssignTarget = "invalid-assign-target", Parse;
312 CompoundAssignToDestructuring = "compound-assign-to-destructuring", Parse;
314 ReferenceAssignToDestructuring = "reference-assign-to-destructuring", Parse;
316 IncDecNonVariable = "inc-dec-non-variable", Parse;
318 InvalidDereference = "invalid-dereference", Parse;
320 InvalidWriteContext = "invalid-write-context", Parse;
322 ListOutsideDestructuring = "list-outside-destructuring", Parse;
324 EmptyArrayElement = "empty-array-element", Compile;
326 ReferenceAssignNew = "reference-assign-new", Parse;
328 HooksOnPropertyList = "hooks-on-property-list", Parse;
330 InvalidInstanceofOperand = "invalid-instanceof-operand", Parse;
332 SwitchLeadingSemicolons = "switch-leading-semicolons", Parse;
334 PartialApplicationInNew = "partial-application-in-new", Parse;
336 PartialApplicationWithUnpacking = "partial-application-with-unpacking", Compile;
338 UnparenthesizedNewDereference = "unparenthesized-new-dereference", Parse;
340 StaticStandaloneExpression = "static-standalone-expression", Parse;
342 ReferenceToParenthesized = "reference-to-parenthesized", Parse;
344 ReferenceToNonReferenceable = "reference-to-non-referenceable", Parse;
346 QualifiedClassKeyword = "qualified-class-keyword", Compile;
348 ReservedClassReference = "reserved-class-reference", Compile;
350 ReservedEnumName = "reserved-enum-name", Compile;
352 TraitInInterface = "trait-in-interface", Compile;
354 AbstractMemberInConcreteClass = "abstract-member-in-concrete-class", Compile;
356 DuplicateProperty = "duplicate-property", Compile;
358 DuplicateMethod = "duplicate-method", Compile;
360 DuplicateClassConstant = "duplicate-class-constant", Compile;
362 ClassConstantNamedClass = "class-constant-named-class", Compile;
364 ClassConstantValueType = "class-constant-value-type", Compile;
366 DefaultValueType = "default-value-type", Compile;
368 PrivateFinalConstant = "private-final-constant", Compile;
370 InvalidInterfaceConstantModifier = "invalid-interface-constant-modifier", Compile;
372 InvalidInterfaceMethodModifier = "invalid-interface-method-modifier", Compile;
374 InvalidInterfacePropertyModifier = "invalid-interface-property-modifier", Compile;
376 InterfaceMethodBody = "interface-method-body", Compile;
378 MissingMethodBody = "missing-method-body", Compile;
380 AbstractMethodBody = "abstract-method-body", Compile;
382 PrivateAbstractMethod = "private-abstract-method", Compile;
384 AbstractAnonymousClassMethod = "abstract-anonymous-class-method", Compile;
386 AbstractEnumMethod = "abstract-enum-method", Compile;
388 MagicMethodArgumentCount = "magic-method-argument-count", Compile;
390 MagicMethodByReference = "magic-method-by-reference", Compile;
392 MagicMethodStatic = "magic-method-static", Compile;
394 MagicMethodParameterType = "magic-method-parameter-type", Compile;
396 StaticReadonlyProperty = "static-readonly-property", Compile;
398 UntypedReadonlyProperty = "untyped-readonly-property", Compile;
400 ReadonlyPropertyHooks = "readonly-property-hooks", Compile;
402 UntypedAsymmetricProperty = "untyped-asymmetric-property", Compile;
404 SetVisibilityWiderThanVisibility = "set-visibility-wider-than-visibility", Compile;
406 PrivateFinalProperty = "private-final-property", Compile;
408 PrivateFinalPropertyHook = "private-final-property-hook", Compile;
410 AbstractPropertyWithoutAbstractHook = "abstract-property-without-abstract-hook", Compile;
412 InterfacePropertyWithoutHooks = "interface-property-without-hooks", Compile;
414 StaticPropertyHooks = "static-property-hooks", Compile;
416 VirtualPropertyDefault = "virtual-property-default", Compile;
418 VirtualPropertyAsymmetricVisibility = "virtual-property-asymmetric-visibility", Compile;
420 EmptyHookList = "empty-hook-list", Compile;
422 DuplicateHook = "duplicate-hook", Compile;
424 GetHookParameters = "get-hook-parameters", Compile;
426 SetHookParameterCount = "set-hook-parameter-count", Compile;
428 SetHookParameterByReference = "set-hook-parameter-by-reference", Compile;
430 SetHookParameterVariadic = "set-hook-parameter-variadic", Compile;
432 SetHookParameterDefault = "set-hook-parameter-default", Compile;
434 SetHookParameterType = "set-hook-parameter-type", Compile;
436 ByReferenceGetHookWithSet = "by-reference-get-hook-with-set", Compile;
438 MissingHookBody = "missing-hook-body", Compile;
440 AbstractHookBody = "abstract-hook-body", Compile;
442 AbstractFinalHook = "abstract-final-hook", Compile;
444 PrivateAbstractHook = "private-abstract-hook", Compile;
446 PromotedPropertyOutsideConstructor = "promoted-property-outside-constructor", Compile;
448 PromotedPropertyInAbstractConstructor = "promoted-property-in-abstract-constructor", Compile;
450 VariadicPromotedProperty = "variadic-promoted-property", Compile;
452 InvalidEnumBackingType = "invalid-enum-backing-type", Compile;
454 EnumCaseNamedClass = "enum-case-named-class", Compile;
456 MissingEnumCaseValue = "missing-enum-case-value", Compile;
458 PureEnumCaseValue = "pure-enum-case-value", Compile;
460 EnumCaseValueType = "enum-case-value-type", Compile;
462 DuplicateEnumCaseValue = "duplicate-enum-case-value", Compile;
464 EnumProperty = "enum-property", Compile;
466 InvalidTraitAliasModifier = "invalid-trait-alias-modifier", Compile;
468 UnterminatedComment = "unterminated-comment", Parse;
470 NestedClassDeclaration = "nested-class-declaration", Compile;
472 MixedNamespaceStyles = "mixed-namespace-styles", Compile;
474 NamespaceNotFirst = "namespace-not-first", Compile;
476 CodeOutsideNamespace = "code-outside-namespace", Compile;
478 NestedNamespace = "nested-namespace", Compile;
480 UseNameConflict = "use-name-conflict", Compile;
482 SpecialClassNameAlias = "special-class-name-alias", Compile;
484 DeclareNotFirstStatement = "declare-not-first-statement", Compile;
486 NonLiteralDeclareValue = "non-literal-declare-value", Compile;
488 InvalidStrictTypes = "invalid-strict-types", Compile;
490 AttributesOnMultipleConstants = "attributes-on-multiple-constants", Compile;
492 AttributeArgumentUnpacking = "attribute-argument-unpacking", Compile;
494 AttributeArgumentClosure = "attribute-argument-closure", Compile;
496 VoidReturnValue = "void-return-value", Compile;
498 NeverReturn = "never-return", Compile;
500 VariadicParameterDefault = "variadic-parameter-default", Compile;
502 VariadicParameterNotLast = "variadic-parameter-not-last", Compile;
504 ThisAsDeclaredVariable = "this-as-declared-variable", Compile;
506 SuperglobalAsDeclaredVariable = "superglobal-as-declared-variable", Compile;
508 DuplicateStaticVariable = "duplicate-static-variable", Compile;
510 BreakOutOfLoop = "break-out-of-loop", Compile;
512 InvalidBreakOperand = "invalid-break-operand", Compile;
514 JumpAcrossFinally = "jump-across-finally", Compile;
516 DuplicateLabel = "duplicate-label", Compile;
518 UndefinedGotoLabel = "undefined-goto-label", Compile;
520 GotoIntoLoop = "goto-into-loop", Compile;
522 DuplicateSwitchDefault = "duplicate-switch-default", Compile;
524 DuplicateMatchDefault = "duplicate-match-default", Compile;
526 ReferenceKey = "reference-key", Compile;
528 ListAsForeachKey = "list-as-foreach-key", Compile;
530 ClassKeywordInCatch = "class-keyword-in-catch", Compile;
532 InvalidParentHookCall = "invalid-parent-hook-call", Compile;
534 StaticClassInConstantExpression = "static-class-in-constant-expression", Compile;
536 AppendOutsideWrite = "append-outside-write", Compile;
538 InvalidGlobalsWrite = "invalid-globals-write", Compile;
540 InvalidNullableType = "invalid-nullable-type", Compile;
542 StandaloneTypeInComposite = "standalone-type-in-composite", Compile;
544 BuiltinTypeInIntersection = "builtin-type-in-intersection", Compile;
546 RedundantType = "redundant-type", Compile;
548 DuplicateType = "duplicate-type", Compile;
550 InvalidParameterType = "invalid-parameter-type", Compile;
552 InvalidMemberType = "invalid-member-type", Compile;
554 QualifiedBuiltinType = "qualified-builtin-type", Compile;
556 ReservedTypeName = "reserved-type-name", Compile;
558 IssetOnExpression = "isset-on-expression", Compile;
560 ClosureForNew = "closure-for-new", Compile;
562 YieldOutsideFunction = "yield-outside-function", Compile;
564 YieldFromInByReferenceGenerator = "yield-from-in-by-reference-generator", Compile;
566 PositionalAfterNamedArgument = "positional-after-named-argument", Compile;
568 PositionalAfterUnpacking = "positional-after-unpacking", Compile;
570 MethodNameNotString = "method-name-not-string", Compile;
572 UnsetCast = "unset-cast", Compile;
574 BuiltinResultWrite = "builtin-result-write", Compile;
576 CallResultWrite = "call-result-write", Compile;
578 TemporaryExpressionWrite = "temporary-expression-write", Compile;
580 NullsafeWrite = "nullsafe-write", Compile;
582 ReferenceToNullsafe = "reference-to-nullsafe", Compile;
584 NullsafeClosureCreation = "nullsafe-closure-creation", Compile;
586 UnwritableDestructuringElement = "unwritable-destructuring-element", Compile;
588 UnsetThis = "unset-this", Compile;
590 ClosureUseShadowsParameter = "closure-use-shadows-parameter", Compile;
592 DuplicateClosureUseVariable = "duplicate-closure-use-variable", Compile;
594 LiteralClassName = "literal-class-name", Compile;
596 ClassFetchOnLiteral = "class-fetch-on-literal", Compile;
598 ClassFetchOnFoldedValue = "class-fetch-on-folded-value", Compile;
600 DynamicClassFetch = "dynamic-class-fetch", Compile;
602 UnsupportedOperandTypes = "unsupported-operand-types", Compile;
604 InvalidConstantExpression = "invalid-constant-expression", Compile;
606 EmptyList = "empty-list", Compile;
608 EmptyKeyedArrayEntry = "empty-keyed-array-entry", Compile;
610 MixedKeyedEntries = "mixed-keyed-entries", Compile;
612 MixedListSyntax = "mixed-list-syntax", Compile;
614 LongArrayInDestructuring = "long-array-in-destructuring", Compile;
616 SpreadInDestructuring = "spread-in-destructuring", Compile;
618 UnpackNonArray = "unpack-non-array", Compile;
620 UnpackStringKeys = "unpack-string-keys", Compile;
622 UnparenthesizedNestedTernary = "unparenthesized-nested-ternary", Compile;
624 UnparenthesizedArrowInPipe = "unparenthesized-arrow-in-pipe", Compile;
626 InvalidCurlyInterpolation = "invalid-curly-interpolation", Compile;
628 RepeatedAttribute = "repeated-attribute", Compile;
630 NoDiscardOnInvalidTarget = "no-discard-on-invalid-target", Compile;
632 NoDiscardWithoutReturnValue = "no-discard-without-return-value", Compile;
634 UnexpectedCharacter = "unexpected-character", Parse;
636 UnexpectedCast = "unexpected-cast", Parse;
638 Other = "other", Compile;
640}
641
642impl std::fmt::Display for ForbiddenRule {
643 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644 f.write_str(self.as_str())
645 }
646}
647
648impl std::str::FromStr for ForbiddenRule {
649 type Err = UnknownForbiddenRule;
650
651 fn from_str(s: &str) -> Result<Self, Self::Err> {
652 ForbiddenRule::ALL
653 .iter()
654 .copied()
655 .find(|rule| rule.as_str() == s)
656 .ok_or(UnknownForbiddenRule)
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::{ForbiddenRule, ParseError, Span};
663 use crate::PhpVersion;
664
665 const VERSION_GATED: &[(ForbiddenRule, PhpVersion, &str, &str)] = &[
667 (
668 ForbiddenRule::EnumCaseValueType,
669 PhpVersion::Php81,
670 "<?php enum E: int { case A = \"a\"; }",
671 "\"a\"",
672 ),
673 (
674 ForbiddenRule::DuplicateEnumCaseValue,
675 PhpVersion::Php81,
676 "<?php enum E: int { case A = 1; case B = 1; }",
677 "1",
678 ),
679 (
680 ForbiddenRule::PartialApplicationWithUnpacking,
681 PhpVersion::Php86,
682 "<?php f(...$a, ?);",
683 "?",
684 ),
685 (
686 ForbiddenRule::NeverReturn,
687 PhpVersion::Php81,
688 "<?php $f = fn(): never => 1;",
689 "1",
690 ),
691 (
692 ForbiddenRule::UnpackStringKeys,
693 PhpVersion::Php80,
694 "<?php $a = [...['a' => 1]];",
695 "['a' => 1]",
696 ),
697 (
698 ForbiddenRule::DynamicClassFetch,
699 PhpVersion::Php74,
700 "<?php $a::class;",
701 "$a",
702 ),
703 (
704 ForbiddenRule::UnsupportedOperandTypes,
705 PhpVersion::Php74,
706 "<?php $a = [] - 1;",
707 "[] - 1",
708 ),
709 (
710 ForbiddenRule::QualifiedBuiltinType,
711 PhpVersion::Php85,
712 "<?php class A { function m(): namespace\\static {} }",
713 "namespace\\static",
714 ),
715 (
716 ForbiddenRule::QualifiedBuiltinType,
717 PhpVersion::Php85,
718 "<?php function f(): \\static {}",
719 "\\static",
720 ),
721 (
722 ForbiddenRule::QualifiedClassKeyword,
723 PhpVersion::Php84,
724 "<?php function f(): \\static {}",
725 "\\static",
726 ),
727 ];
728
729 fn samples(rule: ForbiddenRule) -> &'static [(&'static str, &'static str)] {
731 match rule {
732 ForbiddenRule::ClassKeywordOutsideClass => &[
733 ("<?php function f() { self::g(); }", "self"),
734 ("<?php function f() { return parent::$x::get(); }", "parent"),
735 ],
736 ForbiddenRule::ParentWithoutParentClass => {
737 &[("<?php class A { function f() { parent::f(); } }", "parent")]
738 }
739 ForbiddenRule::ToStringReturnType => &[(
740 "<?php class A { function __toString(): int { return 1; } }",
741 "int",
742 )],
743 ForbiddenRule::MagicMethodReturnType => &[
744 (
745 "<?php class A { function __isset($n): int { return 1; } }",
746 "int",
747 ),
748 ("<?php class A { function __construct(): void {} }", "void"),
749 ],
750 ForbiddenRule::AttributeTargetMismatch => {
751 &[("<?php #[Override] class A {}", "Override")]
752 }
753 ForbiddenRule::AttributeOnInvalidClass => {
754 &[("<?php #[Attribute] trait T {}", "Attribute")]
755 }
756 ForbiddenRule::DeprecatedOnInvalidTarget => {
757 &[("<?php #[Deprecated] class A {}", "Deprecated")]
758 }
759 ForbiddenRule::AllowDynamicPropertiesOnInvalidClass => &[(
760 "<?php #[AllowDynamicProperties] interface I {}",
761 "AllowDynamicProperties",
762 )],
763 ForbiddenRule::MissingReturnValue => {
764 &[("<?php function f(): int { return; }", "return;")]
765 }
766 ForbiddenRule::GeneratorReturnType => &[
767 ("<?php function f(): int { yield 1; }", "int"),
768 (
769 "<?php class A { public int $x { set { yield 1; } } }",
770 "set { yield 1; }",
771 ),
772 ],
773 ForbiddenRule::ReadonlyPropertyDefault => {
774 &[("<?php class A { public readonly int $x = 1; }", "1")]
775 }
776 ForbiddenRule::DuplicateParameter => &[("<?php function f($a, $a) {}", "$a")],
777 ForbiddenRule::ReassignThis => &[
778 ("<?php function f() { $this = 1; }", "$this"),
779 ("<?php try {} catch (E $this) {}", "$this"),
780 ],
781 ForbiddenRule::UnclosedInterpolation => &[("<?php echo \"{$a\";", "{$a\";"), ("<?php echo \"$a[0\";", "[0"), ("<?php echo \"${a\";", "${a\";"),],
782 ForbiddenRule::EmptyInterpolationIndex => &[("<?php echo \"$a[]\";", "[]"), ("<?php echo \"${a[]}\";", "[]"),],
783 ForbiddenRule::EmptyInterpolationVariableName => &[("<?php echo \"${}\";", "${}"),],
784 ForbiddenRule::UnclosedInterpolationIndex => &[("<?php echo \"${a[0]x}\";", "${a[0]x}"),],
785 ForbiddenRule::ClassConstantInterpolation => &[("<?php echo \"{$a::B}\";", "$a::B"),],
786 ForbiddenRule::InvalidInterpolationOffset => &[("<?php echo \"$a[ 1]\";", " 1"),],
787 ForbiddenRule::InvalidNumericLiteral => &[("<?php echo 08;", "08"), ("<?php echo 1_;", "1_"),],
788 ForbiddenRule::InvalidCodepointEscape => &[("<?php echo \"\\u{}\";", "\\u{}"), ("<?php echo \"\\u{110000}\";", "\\u{110000}"),],
789 ForbiddenRule::MixedHeredocIndentation => &[("<?php echo <<<A\n \tx\n \tA;\n", " \t"),],
790 ForbiddenRule::HeredocBodyIndentation => &[("<?php echo <<<A\nx\n A;\n", "x"),],
791 ForbiddenRule::WhitespaceInNamespacedName => &[("<?php echo A \\ B;", "A \\ B"),],
792 ForbiddenRule::MultipleVisibilityModifiers => &[("<?php class A { public private $x; }", "public private"), ("<?php function f() {} class B { function __construct(public private $x) {} }", "public"),],
793 ForbiddenRule::MultipleSetVisibilityModifiers => &[("<?php class A { public private(set) protected(set) int $x; }", "public private(set) protected"), ("<?php class B { function __construct(public private(set) protected(set) int $x) {} }", "public private(set)"),],
794 ForbiddenRule::DuplicateModifier => &[("<?php class A { static static $x; }", "static"), ("<?php class A { public $x { final final get => 1; } }", "final"), ("<?php class B { function __construct(readonly readonly int $x) {} }", "readonly"),],
795 ForbiddenRule::AbstractFinalConflict => &[("<?php abstract final class A {}", "abstract final"), ("<?php abstract class A { abstract final function f(); }", "abstract final"),],
796 ForbiddenRule::InvalidConstantModifier => &[("<?php class A { static const X = 1; }", "const"), ("<?php enum E { static const X = 1; }", "const"),],
797 ForbiddenRule::ModifierNotAllowed => &[("<?php function f(static $x) {}", "static"), ("<?php class A { readonly function f() {} }", "readonly function f() {}"), ("<?php trait T { function f() {} } class A { use T { f as readonly g; } }", "readonly"),],
798 ForbiddenRule::KeywordAsDeclarationName => &[("<?php function list() {}", "list"), ("<?php interface list {}", "list"), ("<?php #[A] enum list {}", "list"),],
799 ForbiddenRule::CustomAssertFunction => &[("<?php namespace A; function Assert() {}", "Assert"),],
800 ForbiddenRule::ReservedClassName => &[("<?php class int {}", "int"),],
801 ForbiddenRule::OutermostScopeOnly => &[("<?php function f() { __halt_compiler(); }", "__halt_compiler"),],
802 ForbiddenRule::UnparenthesizedIntersectionInUnion => &[("<?php function f((A|B)&C $x) {}", "(A|B)"),],
803 ForbiddenRule::ParenthesizedIntersectionOutsideUnion => &[("<?php function f((A&B) $x) {}", "(A&B)"),],
804 ForbiddenRule::StaticTypePosition => &[("<?php class A { public ?static $p; }", "static"), ("<?php function f(?static $x) {}", "static"),],
805 ForbiddenRule::EmptyAttributeList => &[("<?php #[] function f() {}", "#[]"),],
806 ForbiddenRule::CallTimePassByReference => &[("<?php f(&$x);", "&"),],
807 ForbiddenRule::CurlyOffsetAccess => &[("<?php echo $a{0};", "{"),],
808 ForbiddenRule::VoidCastInExpression => &[("<?php $x = (void) f();", "(void) f()"),],
809 ForbiddenRule::RealCast => &[("<?php $x = (real) 1;", "real"),],
810 ForbiddenRule::NonAssociativeChain => &[("<?php $x = 1 < 2 < 3;", "<"),],
811 ForbiddenRule::IncDecAsAssignTarget => &[("<?php $a++ = 1;", "="),],
812 ForbiddenRule::InvalidAssignTarget => &[("<?php 1 = 2;", "="),],
813 ForbiddenRule::CompoundAssignToDestructuring => &[("<?php [$a] += 1;", "+="),],
814 ForbiddenRule::ReferenceAssignToDestructuring => &[("<?php [$a] = &$b;", "&"),],
815 ForbiddenRule::IncDecNonVariable => &[("<?php A++;", "A"),],
816 ForbiddenRule::InvalidDereference => &[("<?php echo 1[0];", "1"),],
817 ForbiddenRule::InvalidWriteContext => &[("<?php unset(A);", "A"),],
818 ForbiddenRule::ListOutsideDestructuring => &[("<?php $x = list($a);", "list($a)"),],
819 ForbiddenRule::EmptyArrayElement => &[("<?php $x = [1, , 2];", ","),],
820 ForbiddenRule::ReferenceAssignNew => &[("<?php $x = &new A;", "new A"),],
821 ForbiddenRule::HooksOnPropertyList => &[("<?php class A { public $x, $y { get => 1; } }", "{"),],
822 ForbiddenRule::InvalidInstanceofOperand => &[("<?php $x instanceof 1;", "1"),],
823 ForbiddenRule::SwitchLeadingSemicolons => &[("<?php switch ($x) {;; case 1: }", ";"),],
824 ForbiddenRule::PartialApplicationInNew => &[("<?php new A(?);", "?"),],
825 ForbiddenRule::UnparenthesizedNewDereference => &[("<?php new A::B;", "::"),],
826 ForbiddenRule::StaticStandaloneExpression => &[("<?php static;", "static"),],
827 ForbiddenRule::ReferenceToParenthesized => &[("<?php $x = &($y);", "($y)"),],
828 ForbiddenRule::ReferenceToNonReferenceable => &[("<?php $x = &1;", "1"), ("<?php [&$a] = 1;", "1"),],
829 ForbiddenRule::QualifiedClassKeyword => &[("<?php try {} catch (\\self $e) {}", "\\self"), ("<?php function f() { return \\self::X; }", "\\self"), ("<?php function f(\\self $x) {}", "\\self"), ("<?php class A extends \\self {}", "\\self"),],
830 ForbiddenRule::ReservedClassReference => &[("<?php class A extends self {}", "self"),],
831 ForbiddenRule::ReservedEnumName => &[("<?php enum int {}", "int"),],
832 ForbiddenRule::TraitInInterface => &[("<?php interface I { use T; }", "use T;"),],
833 ForbiddenRule::AbstractMemberInConcreteClass => &[("<?php class A { abstract function f(); }", "abstract function f();"), ("<?php class A { abstract public $x { get; } }", "abstract public $x { get; }"),],
834 ForbiddenRule::DuplicateProperty => &[("<?php class A { public $x; public $x; }", "public $x"), ("<?php class A { public $x; function __construct(public $x) {} }", "public $x"),],
835 ForbiddenRule::DuplicateMethod => &[("<?php class A { function f() {} function F() {} }", "function F() {}"),],
836 ForbiddenRule::DuplicateClassConstant => &[("<?php class A { const X = 1; const X = 2; }", "X"),],
837 ForbiddenRule::ClassConstantNamedClass => &[("<?php class A { const class = 1; }", "class"),],
838 ForbiddenRule::ClassConstantValueType => &[("<?php class A { const int X = \"a\"; }", "\"a\""),],
839 ForbiddenRule::DefaultValueType => &[("<?php function f(int $x = \"a\") {}", "\"a\""), ("<?php class A { public int $x = null; }", "null"),],
840 ForbiddenRule::PrivateFinalConstant => &[("<?php class A { final private const X = 1; }", "final private const X = 1;"), ("<?php enum E { final private const X = 1; }", "final private const X = 1;"),],
841 ForbiddenRule::InvalidInterfaceConstantModifier => &[("<?php interface I { private const X = 1; }", "private const X = 1;"),],
842 ForbiddenRule::InvalidInterfaceMethodModifier => &[("<?php interface I { private function f(); }", "private function f();"), ("<?php interface I { final function f(); }", "final function f();"), ("<?php interface I { abstract function f(); }", "abstract function f();"),],
843 ForbiddenRule::InvalidInterfacePropertyModifier => &[("<?php interface I { final public $x { get; } }", "final public $x"),],
844 ForbiddenRule::InterfaceMethodBody => &[("<?php interface I { function f() {} }", "function f() {}"),],
845 ForbiddenRule::MissingMethodBody => &[("<?php class A { function f(); }", "function f();"),],
846 ForbiddenRule::AbstractMethodBody => &[("<?php abstract class A { abstract function f() {} }", "abstract function f() {}"),],
847 ForbiddenRule::PrivateAbstractMethod => &[("<?php abstract class A { abstract private function f(); }", "abstract private function f();"),],
848 ForbiddenRule::AbstractAnonymousClassMethod => &[("<?php new class { abstract function f(); };", "abstract function f();"),],
849 ForbiddenRule::AbstractEnumMethod => &[("<?php enum E { abstract function f(); }", "abstract"),],
850 ForbiddenRule::MagicMethodArgumentCount => &[("<?php class A { function __get() {} }", "function __get() {}"),],
851 ForbiddenRule::MagicMethodByReference => &[("<?php class A { function __get(&$n) {} }", "&$n"),],
852 ForbiddenRule::MagicMethodStatic => &[("<?php class A { static function __get($n) {} }", "static function __get($n) {}"),],
853 ForbiddenRule::MagicMethodParameterType => &[("<?php class A { function __get(int $n) {} }", "int"),],
854 ForbiddenRule::StaticReadonlyProperty => &[("<?php class A { public static readonly int $x; }", "public static readonly"), ("<?php class A { public readonly static int $x; }", "public readonly static"),],
855 ForbiddenRule::UntypedReadonlyProperty => &[("<?php class A { public readonly $x; }", "public readonly $x"), ("<?php class A { function __construct(public readonly $x) {} }", "public readonly"),],
856 ForbiddenRule::ReadonlyPropertyHooks => &[("<?php class A { public readonly int $x { get => 1; } }", "get => 1;"), ("<?php class A { function __construct(public readonly int $x { get => 1; }) {} }", "get => 1;"),],
857 ForbiddenRule::UntypedAsymmetricProperty => &[("<?php class A { public private(set) $x; }", "public private(set) $x"), ("<?php class A { function __construct(public private(set) $x) {} }", "public private(set) $x"),],
858 ForbiddenRule::SetVisibilityWiderThanVisibility => &[("<?php class A { private public(set) int $x; }", "private public(set) int $x"), ("<?php class A { function __construct(private public(set) int $x) {} }", "private public(set)"),],
859 ForbiddenRule::PrivateFinalProperty => &[("<?php class A { final private $x; }", "final private $x"),],
860 ForbiddenRule::PrivateFinalPropertyHook => &[("<?php class A { private $x { final get => 1; } }", "final get => 1;"),],
861 ForbiddenRule::AbstractPropertyWithoutAbstractHook => &[("<?php abstract class A { abstract public $x; }", "abstract public $x"), ("<?php abstract class A { abstract public $x { get => 1; } }", "abstract public $x { get => 1; }"),],
862 ForbiddenRule::InterfacePropertyWithoutHooks => &[("<?php interface I { public $x; }", "public $x"),],
863 ForbiddenRule::StaticPropertyHooks => &[("<?php class A { public static $x { get => 1; } }", "public static $x { get => 1; }"),],
864 ForbiddenRule::VirtualPropertyDefault => &[("<?php class A { public int $x = 1 { get => 2; } }", "public int $x = 1 { get => 2; }"),],
865 ForbiddenRule::VirtualPropertyAsymmetricVisibility => &[("<?php class A { public private(set) int $x { get => 1; } }", "public private(set) int $x { get => 1; }"),],
866 ForbiddenRule::EmptyHookList => &[("<?php class A { public $x {} }", "{"),],
867 ForbiddenRule::DuplicateHook => &[("<?php class A { public $x { get => 1; get => 2; } }", "get => 2;"), ("<?php class A { public $x { set {} set {} } }", "set {}"),],
868 ForbiddenRule::GetHookParameters => &[("<?php class A { public $x { get() => 1; } }", "("),],
869 ForbiddenRule::SetHookParameterCount => &[("<?php class A { public $x { set($a, $b) {} } }", "("),],
870 ForbiddenRule::SetHookParameterByReference => &[("<?php class A { public $x { set(&$v) {} } }", "&$v"),],
871 ForbiddenRule::SetHookParameterVariadic => &[("<?php class A { public $x { set(...$v) {} } }", "...$v"),],
872 ForbiddenRule::SetHookParameterDefault => &[("<?php class A { public $x { set($v = 1) {} } }", "$v = 1"),],
873 ForbiddenRule::SetHookParameterType => &[("<?php class A { public int $x { set(string $v) {} } }", "string $v"),],
874 ForbiddenRule::ByReferenceGetHookWithSet => &[("<?php class A { public $x { &get => $this->x; set { $this->x = $value; } } }", "&get => $this->x;"),],
875 ForbiddenRule::MissingHookBody => &[("<?php class A { public $x { get; } }", "get;"),],
876 ForbiddenRule::AbstractHookBody => &[("<?php interface I { public $x { get => 1; } }", "get => 1;"),],
877 ForbiddenRule::AbstractFinalHook => &[("<?php abstract class A { abstract public $x { final get; } }", "final get;"),],
878 ForbiddenRule::PrivateAbstractHook => &[("<?php abstract class A { abstract private $x { get; } }", "get;"),],
879 ForbiddenRule::PromotedPropertyOutsideConstructor => &[("<?php function f(public $x) {}", "public"), ("<?php class A { function f(public $x) {} }", "public"),],
880 ForbiddenRule::PromotedPropertyInAbstractConstructor => &[("<?php abstract class A { abstract function __construct(public $x); }", "public $x"),],
881 ForbiddenRule::VariadicPromotedProperty => &[("<?php class A { function __construct(public ...$x) {} }", "public ...$x"),],
882 ForbiddenRule::InvalidEnumBackingType => &[("<?php enum E: float {}", "float"),],
883 ForbiddenRule::EnumCaseNamedClass => &[("<?php enum E { case class; }", "class"),],
884 ForbiddenRule::MissingEnumCaseValue => &[("<?php enum E: int { case A; }", "A"),],
885 ForbiddenRule::PureEnumCaseValue => &[("<?php enum E { case A = 1; }", "="),],
886 ForbiddenRule::EnumProperty => &[("<?php enum E { public int $a; }", "$a"), ("<?php enum E { var $a, $b; }", "$a"),],
887 ForbiddenRule::InvalidTraitAliasModifier => &[("<?php class A { use T { f as static g; } }", "static"),],
888 ForbiddenRule::UnterminatedComment => &[("<?php /* x", "/* x"),],
889 ForbiddenRule::NestedClassDeclaration => &[("<?php class A { function f() { class B {} } }", "B"),],
890 ForbiddenRule::MixedNamespaceStyles => &[("<?php namespace A; namespace B {}", "namespace B {}"),],
891 ForbiddenRule::NamespaceNotFirst => &[("<?php echo 1; namespace A;", "echo 1;"),],
892 ForbiddenRule::CodeOutsideNamespace => &[("<?php namespace A {} echo 1;", "echo 1;"),],
893 ForbiddenRule::NestedNamespace => &[("<?php namespace A { namespace B {} }", "namespace B {}"),],
894 ForbiddenRule::UseNameConflict => &[("<?php use A\\B; use C\\B;", "C\\B"),],
895 ForbiddenRule::SpecialClassNameAlias => &[("<?php use A as self;", "A as self"),],
896 ForbiddenRule::DeclareNotFirstStatement => &[("<?php echo 1; declare(strict_types=1);", "strict_types=1"), ("<?php echo 1; declare(encoding='UTF-8');", "encoding='UTF-8'"),],
897 ForbiddenRule::NonLiteralDeclareValue => &[("<?php declare(ticks=A);", "ticks=A"), ("<?php declare(encoding=A);", "encoding=A"),],
898 ForbiddenRule::InvalidStrictTypes => &[("<?php declare(strict_types=2);", "strict_types=2"), ("<?php declare(strict_types=1) {}", "strict_types=1"),],
899 ForbiddenRule::AttributesOnMultipleConstants => &[("<?php #[A] const X = 1, Y = 2;", "const X = 1, Y = 2;"),],
900 ForbiddenRule::AttributeArgumentUnpacking => &[("<?php #[A(...$a)] function f() {}", "...$a"),],
901 ForbiddenRule::AttributeArgumentClosure => &[("<?php #[A(...)] function f() {}", "(...)"),],
902 ForbiddenRule::VoidReturnValue => &[("<?php function f(): void { return 1; }", "return 1;"), ("<?php $f = fn(): void => 1;", "1"),],
903 ForbiddenRule::NeverReturn => &[("<?php function f(): never { return; }", "return;"),],
904 ForbiddenRule::VariadicParameterDefault => &[("<?php function f(...$a = []) {}", "...$a = []"),],
905 ForbiddenRule::VariadicParameterNotLast => &[("<?php function f(...$a, $b) {}", "$b"),],
906 ForbiddenRule::ThisAsDeclaredVariable => &[("<?php $f = function () use ($this) {};", "$this"), ("<?php function f($this) {}", "$this"), ("<?php function f() { global $this; }", "$this"), ("<?php function f() { static $this; }", "$this"),],
907 ForbiddenRule::SuperglobalAsDeclaredVariable => &[("<?php $f = function () use ($_GET) {};", "$_GET"), ("<?php function f($_GET) {}", "$_GET"),],
908 ForbiddenRule::DuplicateStaticVariable => &[("<?php function f() { static $a; static $a; }", "$a"),],
909 ForbiddenRule::BreakOutOfLoop => &[("<?php break;", "break"), ("<?php while (1) { break 2; }", "break"),],
910 ForbiddenRule::InvalidBreakOperand => &[("<?php while (1) { break $a; }", "break"), ("<?php while (1) { break 0; }", "break"),],
911 ForbiddenRule::JumpAcrossFinally => &[("<?php while (1) { try {} finally { break; } }", "break"), ("<?php try {} finally { goto a; } a:", "goto a;"),],
912 ForbiddenRule::DuplicateLabel => &[("<?php a: a:", "a:"),],
913 ForbiddenRule::UndefinedGotoLabel => &[("<?php goto a;", "goto a;"),],
914 ForbiddenRule::GotoIntoLoop => &[("<?php goto a; while (1) { a: }", "goto a;"),],
915 ForbiddenRule::DuplicateSwitchDefault => &[("<?php switch ($x) { default: default: }", "default"),],
916 ForbiddenRule::DuplicateMatchDefault => &[("<?php match ($x) { default => 1, default => 2 };", "default"),],
917 ForbiddenRule::ReferenceKey => &[("<?php foreach ($a as &$k => $v) {}", "&$k"),],
918 ForbiddenRule::ListAsForeachKey => &[("<?php foreach ($a as [$k] => $v) {}", "[$k]"),],
919 ForbiddenRule::ClassKeywordInCatch => &[("<?php try {} catch (self $e) {}", "self"),],
920 ForbiddenRule::InvalidParentHookCall => &[("<?php class A extends B { function f() { parent::$x::get(); } }", "parent::$x::get()"),],
921 ForbiddenRule::StaticClassInConstantExpression => &[("<?php class A { const X = static::class; }", "static"),],
922 ForbiddenRule::AppendOutsideWrite => &[("<?php $x = $a[];", "$a[]"), ("<?php unset($a[]);", "$a[]"),],
923 ForbiddenRule::InvalidGlobalsWrite => &[("<?php $GLOBALS[] = 1;", "$GLOBALS[]"), ("<?php $GLOBALS = [];", "$GLOBALS"), ("<?php $x = &$GLOBALS;", "$GLOBALS"),],
924 ForbiddenRule::InvalidNullableType => &[("<?php function f(?mixed $x) {}", "mixed"), ("<?php function f(?null $x) {}", "null"),],
925 ForbiddenRule::StandaloneTypeInComposite => &[("<?php function f(): ?void {}", "void"), ("<?php function f(): void|int {}", "void"),],
926 ForbiddenRule::BuiltinTypeInIntersection => &[("<?php function f(int&A $x) {}", "int"),],
927 ForbiddenRule::RedundantType => &[("<?php function f(object|A $x) {}", "object|A"), ("<?php function f((A&B)|A $x) {}", "A"), ("<?php function f(true|false $x) {}", "true|false"),],
928 ForbiddenRule::DuplicateType => &[("<?php function f(int|int $x) {}", "int"),],
929 ForbiddenRule::InvalidParameterType => &[("<?php function f(void $x) {}", "void"),],
930 ForbiddenRule::InvalidMemberType => &[("<?php class A { public callable $x; }", "callable"),],
931 ForbiddenRule::QualifiedBuiltinType => &[("<?php function f(\\int $x) {}", "\\int"),],
932 ForbiddenRule::ReservedTypeName => &[("<?php function f(A\\int $x) {}", "A\\int"),],
933 ForbiddenRule::IssetOnExpression => &[("<?php isset(f());", "f()"),],
934 ForbiddenRule::ClosureForNew => &[("<?php new A(...);", "(...)"), ("<?php new class(...) {};", "(...)"),],
935 ForbiddenRule::YieldOutsideFunction => &[("<?php yield 1;", "yield"),],
936 ForbiddenRule::YieldFromInByReferenceGenerator => &[("<?php function &f() { yield from g(); }", "yield from g()"),],
937 ForbiddenRule::PositionalAfterNamedArgument => &[("<?php f(a: 1, 2);", "2"),],
938 ForbiddenRule::PositionalAfterUnpacking => &[("<?php f(...$a, 2);", "2"),],
939 ForbiddenRule::MethodNameNotString => &[("<?php $o->{1}();", "1"), ("<?php A::{-1}();", "-1"),],
940 ForbiddenRule::UnsetCast => &[("<?php $x = (unset) 1;", "unset"),],
941 ForbiddenRule::BuiltinResultWrite => &[("<?php (clone $a)->b = 1;", "(clone $a)->b"), ("<?php $x = &f(...);", "f(...)"),],
942 ForbiddenRule::CallResultWrite => &[("<?php f() = 1;", "f()"), ("<?php $a->f()++;", "$a->f()"), ("<?php unset(A::f());", "A::f()"), ("<?php [f(...)] = $a;", "f(...)"), ("<?php foreach ($a as $k => $b?->f()) {}", "$b?->f()"),],
943 ForbiddenRule::TemporaryExpressionWrite => &[("<?php (1)[0] = 2;", "(1)[0]"), ("<?php (1)[0]++;", "(1)[0]"), ("<?php unset((1)[0]);", "(1)[0]"), ("<?php $x = &(1)[0];", "(1)[0]"),],
944 ForbiddenRule::NullsafeWrite => &[("<?php $a?->b = 1;", "$a?->b"), ("<?php $a?->b++;", "$a?->b"), ("<?php unset($a?->b);", "$a?->b"),],
945 ForbiddenRule::ReferenceToNullsafe => &[("<?php $x = &$a?->b;", "$a?->b"), ("<?php [&$x] = $a?->b;", "$a?->b"), ("<?php function &f() { return $a?->b; }", "$a?->b"),],
946 ForbiddenRule::NullsafeClosureCreation => &[("<?php $a?->b(...);", "$a?->b(...)"),],
947 ForbiddenRule::UnwritableDestructuringElement => &[("<?php [A] = $a;", "A"), ("<?php foreach ($a as [A]) {}", "A"),],
948 ForbiddenRule::UnsetThis => &[("<?php function f() { unset($this); }", "$this"),],
949 ForbiddenRule::ClosureUseShadowsParameter => &[("<?php $f = function ($a) use ($a) {};", "$a"),],
950 ForbiddenRule::DuplicateClosureUseVariable => &[("<?php $f = function () use ($a, $a) {};", "$a"),],
951 ForbiddenRule::LiteralClassName => &[("<?php (1)::A;", "(1)"), ("<?php (1)::class;", "(1)"),],
952 ForbiddenRule::ClassFetchOnLiteral => &[("<?php (null)::class;", "(null)"),],
953 ForbiddenRule::ClassFetchOnFoldedValue => &[("<?php echo (-1)::class;", "(-1)"),],
954 ForbiddenRule::InvalidConstantExpression => &[("<?php const X = $a;", "$a"),],
955 ForbiddenRule::EmptyList => &[("<?php [] = $a;", "[]"),],
956 ForbiddenRule::EmptyKeyedArrayEntry => &[("<?php ['a' => $y, , 'b' => $z] = $a;", ","),],
957 ForbiddenRule::MixedKeyedEntries => &[("<?php ['a' => $x, $y] = $a;", "$y"),],
958 ForbiddenRule::MixedListSyntax => &[("<?php [list($a)] = $b;", "list($a)"),],
959 ForbiddenRule::LongArrayInDestructuring => &[("<?php [array($a)] = $b;", "array($a)"),],
960 ForbiddenRule::SpreadInDestructuring => &[("<?php [...$a] = $b;", "...$a"),],
961 ForbiddenRule::UnpackNonArray => &[("<?php $a = [1, ...2];", "2"),],
962 ForbiddenRule::UnparenthesizedNestedTernary => &[("<?php $x = 1 ? 2 : 3 ? 4 : 5;", "?"),],
963 ForbiddenRule::UnparenthesizedArrowInPipe => &[("<?php $x = 1 |> fn($a) => $a;", "fn($a) => $a"),],
964 ForbiddenRule::InvalidCurlyInterpolation => &[("<?php echo \"{$a + 1}\";", "$a + 1"),],
965 ForbiddenRule::RepeatedAttribute => &[("<?php #[Override, Override] function f() {}", "Override"),],
966 ForbiddenRule::NoDiscardOnInvalidTarget => &[("<?php class A { public $x { #[NoDiscard] get => 1; } }", "NoDiscard"), ("<?php class A { #[NoDiscard] function __construct() {} }", "NoDiscard"),],
967 ForbiddenRule::NoDiscardWithoutReturnValue => &[("<?php #[NoDiscard] function f(): void {}", "NoDiscard"),],
968 ForbiddenRule::UnexpectedCharacter => &[("<?php $a\x01\x02 = 1;", "\x01\x02"), ("<?php $a =\x0c1;", "\x0c"),],
969 ForbiddenRule::UnexpectedCast => &[("<?php foo(int);", "(int)"), ("<?php if ( float ) {}", "( float )"),],
970 ForbiddenRule::DynamicClassFetch
971 | ForbiddenRule::UnpackStringKeys
972 | ForbiddenRule::UnsupportedOperandTypes
973 | ForbiddenRule::PartialApplicationWithUnpacking
974 | ForbiddenRule::EnumCaseValueType
975 | ForbiddenRule::DuplicateEnumCaseValue
976 | ForbiddenRule::Other => &[],
977 }
978 }
979
980 #[test]
981 fn each_rule_reports_its_span() {
982 for &rule in ForbiddenRule::ALL {
983 if rule == ForbiddenRule::Other {
984 continue;
985 }
986 let gated = VERSION_GATED.iter().any(|&(r, ..)| r == rule);
987 assert!(gated || !samples(rule).is_empty(), "{rule:?} has no sample");
988 for &(src, text) in samples(rule) {
989 let errors = crate::parse(src).errors;
990 let spans: Vec<&str> = errors
991 .iter()
992 .filter(|e| e.forbidden_rule() == Some(rule))
993 .map(|e| &src[e.span().start as usize..e.span().end as usize])
994 .collect();
995 assert_eq!(spans, [text], "{rule:?} in {src:?}: {errors:?}");
996 }
997 }
998 }
999
1000 #[test]
1001 fn version_gated_rules_report_their_span() {
1002 for &(rule, version, src, text) in VERSION_GATED {
1003 let options = crate::ParseOptions::default().version(version);
1004 let errors = crate::parse_with(src, &options).errors;
1005 let spans: Vec<&str> = errors
1006 .iter()
1007 .filter(|e| e.forbidden_rule() == Some(rule))
1008 .map(|e| &src[e.span().start as usize..e.span().end as usize])
1009 .collect();
1010 assert_eq!(spans, [text], "{rule:?} in {src:?}: {errors:?}");
1011 }
1012 }
1013
1014 #[test]
1015 fn relative_static_type_is_valid_in_a_class_before_8_5() {
1016 let options = crate::ParseOptions::default().version(PhpVersion::Php84);
1017 let src = "<?php class A { function m(): namespace\\static {} }";
1018 assert_eq!(crate::parse_with(src, &options).errors.len(), 0);
1019 }
1020
1021 #[test]
1022 fn identifiers_are_unique_and_parse_back() {
1023 let mut ids: Vec<&str> = ForbiddenRule::ALL.iter().map(|r| r.as_str()).collect();
1024 ids.sort_unstable();
1025 ids.dedup();
1026 assert_eq!(ids.len(), ForbiddenRule::ALL.len());
1027 for &rule in ForbiddenRule::ALL {
1028 assert_eq!(rule.as_str().parse(), Ok(rule));
1029 }
1030 assert_eq!(
1031 "no-such-rule".parse::<ForbiddenRule>(),
1032 Err(super::UnknownForbiddenRule)
1033 );
1034 }
1035
1036 #[test]
1037 fn codes_are_unique_and_only_syntax_errors_leave_the_ast_incomplete() {
1038 let span = Span::new(0, 0);
1039 let incomplete = [
1040 ParseError::Expected {
1041 expected: "';'".into(),
1042 found: "'}'".into(),
1043 span,
1044 },
1045 ParseError::UnterminatedString { span },
1046 ParseError::ExpectedAfter {
1047 expected: "';'".into(),
1048 after: "expression".into(),
1049 span,
1050 },
1051 ParseError::UnclosedDelimiter {
1052 delimiter: "')'".into(),
1053 opened_at: span,
1054 span,
1055 },
1056 ParseError::UnmatchedDelimiter {
1057 delimiter: "'}'".into(),
1058 span,
1059 },
1060 ParseError::NestingTooDeep { span },
1061 ParseError::SourceTooLarge { len: 0, span },
1062 ];
1063 let complete = [
1064 ParseError::Forbidden {
1065 rule: ForbiddenRule::ReassignThis,
1066 message: "Cannot re-assign $this".into(),
1067 span,
1068 },
1069 ParseError::VersionTooLow {
1070 feature: "match".into(),
1071 required: PhpVersion::Php80,
1072 used: PhpVersion::Php74,
1073 span,
1074 },
1075 ];
1076 assert!(incomplete.iter().all(ParseError::leaves_ast_incomplete));
1077 assert!(!complete.iter().any(ParseError::leaves_ast_incomplete));
1078 assert_eq!(complete[0].code(), "reassign-this");
1079 let mut codes: Vec<&str> = ForbiddenRule::ALL.iter().map(|r| r.as_str()).collect();
1080 codes.extend(
1081 incomplete
1082 .iter()
1083 .chain(&complete[1..])
1084 .map(ParseError::code),
1085 );
1086 let count = codes.len();
1087 codes.sort_unstable();
1088 codes.dedup();
1089 assert_eq!(codes.len(), count);
1090 }
1091
1092 #[test]
1093 fn rule_adds_no_size() {
1094 assert_eq!(std::mem::size_of::<ParseError>(), 64);
1095 }
1096}