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