Skip to content

Commit 3b615f2

Browse files
authored
Merge pull request #1160 from PHPCSStandards/php-8.4/tokenizer-php-fix-anon-class-deref-vs-short-array
PHP 8.4 | Tokenizer/PHP: allow for anon class dereferencing
2 parents 0d3e645 + 42e33b7 commit 3b615f2

File tree

3 files changed

+68
-23
lines changed

3 files changed

+68
-23
lines changed

src/Tokenizers/PHP.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3127,6 +3127,29 @@ protected function processAdditional()
31273127
$allowed += Tokens::$magicConstants;
31283128

31293129
for ($x = ($i - 1); $x >= 0; $x--) {
3130+
// Allow for PHP 8.4 anon class dereferencing without wrapping parentheses.
3131+
// Note: the T_CLASS token has not yet been retokenized to T_ANON_CLASS!
3132+
if ($this->tokens[$x]['code'] === T_CLOSE_CURLY_BRACKET
3133+
&& isset($this->tokens[$x]['scope_condition']) === true
3134+
&& $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_CLASS
3135+
) {
3136+
// Now, make sure it is an anonymous class and not a normal class.
3137+
for ($y = ($this->tokens[$x]['scope_condition'] + 1); $y < $numTokens; $y++) {
3138+
if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
3139+
break;
3140+
}
3141+
}
3142+
3143+
// Use the same check as used for the anon class retokenization.
3144+
if ($this->tokens[$y]['code'] === T_OPEN_PARENTHESIS
3145+
|| $this->tokens[$y]['code'] === T_OPEN_CURLY_BRACKET
3146+
|| $this->tokens[$y]['code'] === T_EXTENDS
3147+
|| $this->tokens[$y]['code'] === T_IMPLEMENTS
3148+
) {
3149+
break;
3150+
}
3151+
}
3152+
31303153
// If we hit a scope opener, the statement has ended
31313154
// without finding anything, so it's probably an array
31323155
// using PHP 7.1 short list syntax.

tests/Core/Tokenizers/PHP/ShortArrayTest.inc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ $var = $obj?->function_call()[$x];
6868
/* testInterpolatedStringDereferencing */
6969
$var = "PHP{$rocks}"[1];
7070

71+
/* testNewAnonClassNoParenthesesExpressionDereferencing */
72+
$a = new class {}[0];
73+
74+
$a = new class (['value'])
75+
/* testNewAnonClassParenthesesExpressionDereferencing */
76+
{}[0];
77+
78+
/* testNewAnonClassExtendsExpressionDereferencing */
79+
$a = new readonly class extends ArrayObject {}[0];
80+
81+
/* testNewAnonClassImplementsExpressionDereferencing */
82+
$a = new class implements ArrayAccess {}[0];
83+
7184
/*
7285
* Short array brackets.
7386
*/
@@ -106,6 +119,10 @@ if ( true ) :
106119
[ $a ] = [ 'hi' ];
107120
endif;
108121

122+
class Foo extends ArrayObject {}
123+
/* testShortListDeclarationAfterClassDeclaration */
124+
[$a] = ['hi'];
125+
109126
/* testLiveCoding */
110127
// Intentional parse error. This has to be the last test in the file.
111128
$array = [

tests/Core/Tokenizers/PHP/ShortArrayTest.php

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,33 @@ public function testSquareBrackets($testMarker)
5555
public static function dataSquareBrackets()
5656
{
5757
return [
58-
'array access 1' => ['/* testArrayAccess1 */'],
59-
'array access 2' => ['/* testArrayAccess2 */'],
60-
'array assignment' => ['/* testArrayAssignment */'],
61-
'function call dereferencing' => ['/* testFunctionCallDereferencing */'],
62-
'method call dereferencing' => ['/* testMethodCallDereferencing */'],
63-
'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'],
64-
'property dereferencing' => ['/* testPropertyDereferencing */'],
65-
'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'],
66-
'static property dereferencing' => ['/* testStaticPropertyDereferencing */'],
67-
'string dereferencing single quotes' => ['/* testStringDereferencing */'],
68-
'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'],
69-
'global constant dereferencing' => ['/* testConstantDereferencing */'],
70-
'class constant dereferencing' => ['/* testClassConstantDereferencing */'],
71-
'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'],
72-
'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'],
73-
'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'],
74-
'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'],
75-
'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'],
76-
'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'],
77-
'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'],
78-
'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'],
79-
'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'],
80-
'live coding' => ['/* testLiveCoding */'],
58+
'array access 1' => ['/* testArrayAccess1 */'],
59+
'array access 2' => ['/* testArrayAccess2 */'],
60+
'array assignment' => ['/* testArrayAssignment */'],
61+
'function call dereferencing' => ['/* testFunctionCallDereferencing */'],
62+
'method call dereferencing' => ['/* testMethodCallDereferencing */'],
63+
'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'],
64+
'property dereferencing' => ['/* testPropertyDereferencing */'],
65+
'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'],
66+
'static property dereferencing' => ['/* testStaticPropertyDereferencing */'],
67+
'string dereferencing single quotes' => ['/* testStringDereferencing */'],
68+
'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'],
69+
'global constant dereferencing' => ['/* testConstantDereferencing */'],
70+
'class constant dereferencing' => ['/* testClassConstantDereferencing */'],
71+
'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'],
72+
'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'],
73+
'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'],
74+
'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'],
75+
'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'],
76+
'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'],
77+
'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'],
78+
'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'],
79+
'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'],
80+
'new anonymous class expression dereferencing 1' => ['/* testNewAnonClassNoParenthesesExpressionDereferencing */'],
81+
'new anonymous class expression dereferencing 2' => ['/* testNewAnonClassParenthesesExpressionDereferencing */'],
82+
'new anonymous class expression dereferencing 3' => ['/* testNewAnonClassExtendsExpressionDereferencing */'],
83+
'new anonymous class expression dereferencing 4' => ['/* testNewAnonClassImplementsExpressionDereferencing */'],
84+
'live coding' => ['/* testLiveCoding */'],
8185
];
8286

8387
}//end dataSquareBrackets()
@@ -133,6 +137,7 @@ public static function dataShortArrays()
133137
'short list after braced control structure' => ['/* testShortListDeclarationAfterBracedControlStructure */'],
134138
'short list after non-braced control structure' => ['/* testShortListDeclarationAfterNonBracedControlStructure */'],
135139
'short list after alternative control structure' => ['/* testShortListDeclarationAfterAlternativeControlStructure */'],
140+
'short list after class declaration' => ['/* testShortListDeclarationAfterClassDeclaration */'],
136141
];
137142

138143
}//end dataShortArrays()

0 commit comments

Comments
 (0)