Skip to content

Commit baaf896

Browse files
committed
Tokenizer: assign a parenthesis_owner for anonymous classes with parenthesis
Code: ```php $anonClass = new class($arg) {}; ``` As anonymous classes have a distinct token, it makes sense to me to assign that token as the parenthesis owner, along the same lines as is done for anonymous functions. The main difference is that for anonymous classes, the parenthesis are optional. Assigning an owner for them can not be done from within the `Tokenizer::createTokenMap()` as at that point in time the `PHP::processAdditional()` method hasn't run yet, so the token is still a `T_CLASS`. It can however be adjusted from within the `PHP::processAdditional()` method when the `T_CLASS` token is changed to a `T_ANON_CLASS` token. This commit implements this. This means that for the `class` token in the above code snippet will now have a `parenthesis_owner`, `parenthesis_opener` and `parenthesis_closer` in the `$tokens` array. The parenthesis opener and closer will both now also have the `parenthesis_owner` key, in addition to the `parenthesis_opener` and `parenthesis_closer` keys which they already had. Instead of testing this via existing sniffs, I have chosen to add a new set of `Core` tests for specific tokenizer issues, with the tests for this change being the first set of tests added. Includes: * Adding the `T_ANON_CLASS` token to the `Tokens::$parenthesisOpeners` array. * Removing the `T_ANON_CLASS` from the "additional tokens indicating that parenthesis are not arbitrary" list in the `Generic.WhiteSpace.ArbitraryParenthesesSpacing` sniff.
1 parent aafc304 commit baaf896

File tree

6 files changed

+204
-14
lines changed

6 files changed

+204
-14
lines changed

package.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ http://pear.php.net/dtd/package-2.0.xsd">
209209
<file baseinstalldir="" name="AcceptTest.php" role="test" />
210210
</dir>
211211
</dir>
212+
<dir name="Tokenizer">
213+
<dir name="PHP">
214+
<file baseinstalldir="" name="T_AnonClassParenthesisOwnerTest.inc" role="test" />
215+
<file baseinstalldir="" name="T_AnonClassParenthesisOwnerTest.php" role="test" />
216+
</dir>
217+
</dir>
212218
<file baseinstalldir="" name="AbstractMethodUnitTest.php" role="test" />
213219
<file baseinstalldir="" name="AllTests.php" role="test" />
214220
<file baseinstalldir="" name="ErrorSuppressionTest.php" role="test" />
@@ -1988,6 +1994,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
19881994
<install as="CodeSniffer/Core/File/IsReferenceTest.inc" name="tests/Core/File/IsReferenceTest.inc" />
19891995
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.php" name="tests/Core/Filters/Filter/AcceptTest.php" />
19901996
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.xml" name="tests/Core/Filters/Filter/AcceptTest.xml" />
1997+
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" />
1998+
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" />
19911999
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
19922000
<install as="CodeSniffer/Standards/AbstractSniffUnitTest.php" name="tests/Standards/AbstractSniffUnitTest.php" />
19932001
</filelist>
@@ -2025,6 +2033,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
20252033
<install as="CodeSniffer/Core/File/IsReferenceTest.inc" name="tests/Core/File/IsReferenceTest.inc" />
20262034
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.php" name="tests/Core/Filters/Filter/AcceptTest.php" />
20272035
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.xml" name="tests/Core/Filters/Filter/AcceptTest.xml" />
2036+
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" />
2037+
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" />
20282038
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
20292039
<install as="CodeSniffer/Standards/AbstractSniffUnitTest.php" name="tests/Standards/AbstractSniffUnitTest.php" />
20302040
<ignore name="bin/phpcs.bat" />

src/Standards/Generic/Sniffs/WhiteSpace/ArbitraryParenthesesSpacingSniff.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ public function register()
5757
$this->ignoreTokens[T_CLOSE_SQUARE_BRACKET] = T_CLOSE_SQUARE_BRACKET;
5858
$this->ignoreTokens[T_CLOSE_SHORT_ARRAY] = T_CLOSE_SHORT_ARRAY;
5959

60-
$this->ignoreTokens[T_ANON_CLASS] = T_ANON_CLASS;
6160
$this->ignoreTokens[T_USE] = T_USE;
6261
$this->ignoreTokens[T_DECLARE] = T_DECLARE;
6362
$this->ignoreTokens[T_THROW] = T_THROW;

src/Tokenizers/PHP.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,23 @@ protected function processAdditional()
15591559
echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL;
15601560
}
15611561

1562+
if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
1563+
&& isset($this->tokens[$x]['parenthesis_closer']) === true
1564+
) {
1565+
$closer = $this->tokens[$x]['parenthesis_closer'];
1566+
1567+
$this->tokens[$i]['parenthesis_opener'] = $x;
1568+
$this->tokens[$i]['parenthesis_closer'] = $closer;
1569+
$this->tokens[$i]['parenthesis_owner'] = $i;
1570+
$this->tokens[$x]['parenthesis_owner'] = $i;
1571+
$this->tokens[$closer]['parenthesis_owner'] = $i;
1572+
1573+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1574+
$line = $this->tokens[$i]['line'];
1575+
echo "\t\t* added parenthesis keys to T_ANON_CLASS token $i on line $line".PHP_EOL;
1576+
}
1577+
}
1578+
15621579
for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
15631580
if (isset($this->tokens[$x]['conditions'][$i]) === false) {
15641581
continue;
@@ -1570,7 +1587,7 @@ protected function processAdditional()
15701587
echo "\t\t* cleaned $x ($type) *".PHP_EOL;
15711588
}
15721589
}
1573-
}
1590+
}//end if
15741591

15751592
continue;
15761593
} else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {

src/Util/Tokens.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -344,18 +344,19 @@ final class Tokens
344344
* @var array<int, int>
345345
*/
346346
public static $parenthesisOpeners = [
347-
T_ARRAY => T_ARRAY,
348-
T_LIST => T_LIST,
349-
T_FUNCTION => T_FUNCTION,
350-
T_CLOSURE => T_CLOSURE,
351-
T_WHILE => T_WHILE,
352-
T_FOR => T_FOR,
353-
T_FOREACH => T_FOREACH,
354-
T_SWITCH => T_SWITCH,
355-
T_IF => T_IF,
356-
T_ELSEIF => T_ELSEIF,
357-
T_CATCH => T_CATCH,
358-
T_DECLARE => T_DECLARE,
347+
T_ARRAY => T_ARRAY,
348+
T_LIST => T_LIST,
349+
T_FUNCTION => T_FUNCTION,
350+
T_CLOSURE => T_CLOSURE,
351+
T_ANON_CLASS => T_ANON_CLASS,
352+
T_WHILE => T_WHILE,
353+
T_FOR => T_FOR,
354+
T_FOREACH => T_FOREACH,
355+
T_SWITCH => T_SWITCH,
356+
T_IF => T_IF,
357+
T_ELSEIF => T_ELSEIF,
358+
T_CATCH => T_CATCH,
359+
T_DECLARE => T_DECLARE,
359360
];
360361

361362
/**
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/* testNoParentheses */
4+
$anonClass = new class {
5+
function __construct() {}
6+
};
7+
8+
/* testNoParenthesesAndEmptyTokens */
9+
$anonClass = new class // phpcs:ignore Standard.Cat
10+
{
11+
function __construct() {}
12+
};
13+
14+
/* testWithParentheses */
15+
$anonClass = new class() {};
16+
17+
/* testWithParenthesesAndEmptyTokens */
18+
$anonClass = new class /*comment */
19+
() {};
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
/**
3+
* Tests the adding of the "parenthesis" keys to an anonymous class token.
4+
*
5+
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
6+
* @copyright 2019 Squiz Pty Ltd (ABN 77 084 670 600)
7+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8+
*/
9+
10+
namespace PHP_CodeSniffer\Tests\Core\Tokenizer\PHP;
11+
12+
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
13+
14+
class T_AnonClassParenthesisOwnerTest extends AbstractMethodUnitTest
15+
{
16+
17+
18+
/**
19+
* Test that anonymous class tokens without parenthesis do not get assigned a parenthesis owner.
20+
*
21+
* @param string $testMarker The comment which prefaces the target token in the test file.
22+
*
23+
* @dataProvider dataAnonClassNoParentheses
24+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
25+
*
26+
* @return void
27+
*/
28+
public function testAnonClassNoParentheses($testMarker)
29+
{
30+
$tokens = self::$phpcsFile->getTokens();
31+
32+
$anonClass = $this->getTargetToken($testMarker, T_ANON_CLASS);
33+
$this->assertFalse(array_key_exists('parenthesis_owner', $tokens[$anonClass]));
34+
$this->assertFalse(array_key_exists('parenthesis_opener', $tokens[$anonClass]));
35+
$this->assertFalse(array_key_exists('parenthesis_closer', $tokens[$anonClass]));
36+
37+
}//end testAnonClassNoParentheses()
38+
39+
40+
/**
41+
* Test that the next open/close parenthesis after an anonymous class without parenthesis
42+
* do not get assigned the anonymous class as a parenthesis owner.
43+
*
44+
* @param string $testMarker The comment which prefaces the target token in the test file.
45+
*
46+
* @dataProvider dataAnonClassNoParentheses
47+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
48+
*
49+
* @return void
50+
*/
51+
public function testAnonClassNoParenthesesNextOpenClose($testMarker)
52+
{
53+
$tokens = self::$phpcsFile->getTokens();
54+
$function = $this->getTargetToken($testMarker, T_FUNCTION);
55+
56+
$opener = $this->getTargetToken($testMarker, T_OPEN_PARENTHESIS);
57+
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]));
58+
$this->assertSame($function, $tokens[$opener]['parenthesis_owner']);
59+
60+
$closer = $this->getTargetToken($testMarker, T_CLOSE_PARENTHESIS);
61+
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]));
62+
$this->assertSame($function, $tokens[$closer]['parenthesis_owner']);
63+
64+
}//end testAnonClassNoParenthesesNextOpenClose()
65+
66+
67+
/**
68+
* Data provider.
69+
*
70+
* @see testAnonClassNoParentheses()
71+
* @see testAnonClassNoParenthesesNextOpenClose()
72+
*
73+
* @return array
74+
*/
75+
public function dataAnonClassNoParentheses()
76+
{
77+
return [
78+
['/* testNoParentheses */'],
79+
['/* testNoParenthesesAndEmptyTokens */'],
80+
];
81+
82+
}//end dataAnonClassNoParentheses()
83+
84+
85+
/**
86+
* Test that anonymous class tokens with parenthesis get assigned a parenthesis owner,
87+
* opener and closer; and that the opener/closer get the anonymous class assigned as owner.
88+
*
89+
* @param string $testMarker The comment which prefaces the target token in the test file.
90+
*
91+
* @dataProvider dataAnonClassWithParentheses
92+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
93+
*
94+
* @return void
95+
*/
96+
public function testAnonClassWithParentheses($testMarker)
97+
{
98+
$tokens = self::$phpcsFile->getTokens();
99+
$anonClass = $this->getTargetToken($testMarker, T_ANON_CLASS);
100+
$opener = $this->getTargetToken($testMarker, T_OPEN_PARENTHESIS);
101+
$closer = $this->getTargetToken($testMarker, T_CLOSE_PARENTHESIS);
102+
103+
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$anonClass]));
104+
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$anonClass]));
105+
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$anonClass]));
106+
$this->assertSame($anonClass, $tokens[$anonClass]['parenthesis_owner']);
107+
$this->assertSame($opener, $tokens[$anonClass]['parenthesis_opener']);
108+
$this->assertSame($closer, $tokens[$anonClass]['parenthesis_closer']);
109+
110+
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]));
111+
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$opener]));
112+
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$opener]));
113+
$this->assertSame($anonClass, $tokens[$opener]['parenthesis_owner']);
114+
$this->assertSame($opener, $tokens[$opener]['parenthesis_opener']);
115+
$this->assertSame($closer, $tokens[$opener]['parenthesis_closer']);
116+
117+
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]));
118+
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$closer]));
119+
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$closer]));
120+
$this->assertSame($anonClass, $tokens[$closer]['parenthesis_owner']);
121+
$this->assertSame($opener, $tokens[$closer]['parenthesis_opener']);
122+
$this->assertSame($closer, $tokens[$closer]['parenthesis_closer']);
123+
124+
}//end testAnonClassWithParentheses()
125+
126+
127+
/**
128+
* Data provider.
129+
*
130+
* @see testAnonClassWithParentheses()
131+
*
132+
* @return array
133+
*/
134+
public function dataAnonClassWithParentheses()
135+
{
136+
return [
137+
['/* testWithParentheses */'],
138+
['/* testWithParenthesesAndEmptyTokens */'],
139+
];
140+
141+
}//end dataAnonClassWithParentheses()
142+
143+
144+
}//end class

0 commit comments

Comments
 (0)