diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 32877a0ef7..2fdabbfdab 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -3127,6 +3127,29 @@ protected function processAdditional() $allowed += Tokens::$magicConstants; for ($x = ($i - 1); $x >= 0; $x--) { + // Allow for PHP 8.4 anon class dereferencing without wrapping parentheses. + // Note: the T_CLASS token has not yet been retokenized to T_ANON_CLASS! + if ($this->tokens[$x]['code'] === T_CLOSE_CURLY_BRACKET + && isset($this->tokens[$x]['scope_condition']) === true + && $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_CLASS + ) { + // Now, make sure it is an anonymous class and not a normal class. + for ($y = ($this->tokens[$x]['scope_condition'] + 1); $y < $numTokens; $y++) { + if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) { + break; + } + } + + // Use the same check as used for the anon class retokenization. + if ($this->tokens[$y]['code'] === T_OPEN_PARENTHESIS + || $this->tokens[$y]['code'] === T_OPEN_CURLY_BRACKET + || $this->tokens[$y]['code'] === T_EXTENDS + || $this->tokens[$y]['code'] === T_IMPLEMENTS + ) { + break; + } + } + // If we hit a scope opener, the statement has ended // without finding anything, so it's probably an array // using PHP 7.1 short list syntax. diff --git a/tests/Core/Tokenizers/PHP/ShortArrayTest.inc b/tests/Core/Tokenizers/PHP/ShortArrayTest.inc index 60b23a51cc..cf3b91eee9 100644 --- a/tests/Core/Tokenizers/PHP/ShortArrayTest.inc +++ b/tests/Core/Tokenizers/PHP/ShortArrayTest.inc @@ -68,6 +68,19 @@ $var = $obj?->function_call()[$x]; /* testInterpolatedStringDereferencing */ $var = "PHP{$rocks}"[1]; +/* testNewAnonClassNoParenthesesExpressionDereferencing */ +$a = new class {}[0]; + +$a = new class (['value']) + /* testNewAnonClassParenthesesExpressionDereferencing */ + {}[0]; + +/* testNewAnonClassExtendsExpressionDereferencing */ +$a = new readonly class extends ArrayObject {}[0]; + +/* testNewAnonClassImplementsExpressionDereferencing */ +$a = new class implements ArrayAccess {}[0]; + /* * Short array brackets. */ @@ -106,6 +119,10 @@ if ( true ) : [ $a ] = [ 'hi' ]; endif; +class Foo extends ArrayObject {} +/* testShortListDeclarationAfterClassDeclaration */ +[$a] = ['hi']; + /* testLiveCoding */ // Intentional parse error. This has to be the last test in the file. $array = [ diff --git a/tests/Core/Tokenizers/PHP/ShortArrayTest.php b/tests/Core/Tokenizers/PHP/ShortArrayTest.php index da9c7c1040..456ee4796e 100644 --- a/tests/Core/Tokenizers/PHP/ShortArrayTest.php +++ b/tests/Core/Tokenizers/PHP/ShortArrayTest.php @@ -55,29 +55,33 @@ public function testSquareBrackets($testMarker) public static function dataSquareBrackets() { return [ - 'array access 1' => ['/* testArrayAccess1 */'], - 'array access 2' => ['/* testArrayAccess2 */'], - 'array assignment' => ['/* testArrayAssignment */'], - 'function call dereferencing' => ['/* testFunctionCallDereferencing */'], - 'method call dereferencing' => ['/* testMethodCallDereferencing */'], - 'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'], - 'property dereferencing' => ['/* testPropertyDereferencing */'], - 'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'], - 'static property dereferencing' => ['/* testStaticPropertyDereferencing */'], - 'string dereferencing single quotes' => ['/* testStringDereferencing */'], - 'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'], - 'global constant dereferencing' => ['/* testConstantDereferencing */'], - 'class constant dereferencing' => ['/* testClassConstantDereferencing */'], - 'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'], - 'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'], - 'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'], - 'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'], - 'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'], - 'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'], - 'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'], - 'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'], - 'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'], - 'live coding' => ['/* testLiveCoding */'], + 'array access 1' => ['/* testArrayAccess1 */'], + 'array access 2' => ['/* testArrayAccess2 */'], + 'array assignment' => ['/* testArrayAssignment */'], + 'function call dereferencing' => ['/* testFunctionCallDereferencing */'], + 'method call dereferencing' => ['/* testMethodCallDereferencing */'], + 'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'], + 'property dereferencing' => ['/* testPropertyDereferencing */'], + 'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'], + 'static property dereferencing' => ['/* testStaticPropertyDereferencing */'], + 'string dereferencing single quotes' => ['/* testStringDereferencing */'], + 'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'], + 'global constant dereferencing' => ['/* testConstantDereferencing */'], + 'class constant dereferencing' => ['/* testClassConstantDereferencing */'], + 'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'], + 'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'], + 'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'], + 'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'], + 'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'], + 'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'], + 'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'], + 'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'], + 'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'], + 'new anonymous class expression dereferencing 1' => ['/* testNewAnonClassNoParenthesesExpressionDereferencing */'], + 'new anonymous class expression dereferencing 2' => ['/* testNewAnonClassParenthesesExpressionDereferencing */'], + 'new anonymous class expression dereferencing 3' => ['/* testNewAnonClassExtendsExpressionDereferencing */'], + 'new anonymous class expression dereferencing 4' => ['/* testNewAnonClassImplementsExpressionDereferencing */'], + 'live coding' => ['/* testLiveCoding */'], ]; }//end dataSquareBrackets() @@ -133,6 +137,7 @@ public static function dataShortArrays() 'short list after braced control structure' => ['/* testShortListDeclarationAfterBracedControlStructure */'], 'short list after non-braced control structure' => ['/* testShortListDeclarationAfterNonBracedControlStructure */'], 'short list after alternative control structure' => ['/* testShortListDeclarationAfterAlternativeControlStructure */'], + 'short list after class declaration' => ['/* testShortListDeclarationAfterClassDeclaration */'], ]; }//end dataShortArrays()