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