Skip to content

Commit

Permalink
Tokenizer: assign a parenthesis_owner for anonymous classes with pare…
Browse files Browse the repository at this point in the history
…nthesis

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.
  • Loading branch information
jrfnl committed Aug 29, 2019
1 parent aafc304 commit baaf896
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 14 deletions.
10 changes: 10 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="AcceptTest.php" role="test" />
</dir>
</dir>
<dir name="Tokenizer">
<dir name="PHP">
<file baseinstalldir="" name="T_AnonClassParenthesisOwnerTest.inc" role="test" />
<file baseinstalldir="" name="T_AnonClassParenthesisOwnerTest.php" role="test" />
</dir>
</dir>
<file baseinstalldir="" name="AbstractMethodUnitTest.php" role="test" />
<file baseinstalldir="" name="AllTests.php" role="test" />
<file baseinstalldir="" name="ErrorSuppressionTest.php" role="test" />
Expand Down Expand Up @@ -1988,6 +1994,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/File/IsReferenceTest.inc" name="tests/Core/File/IsReferenceTest.inc" />
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.php" name="tests/Core/Filters/Filter/AcceptTest.php" />
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.xml" name="tests/Core/Filters/Filter/AcceptTest.xml" />
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" />
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" />
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
<install as="CodeSniffer/Standards/AbstractSniffUnitTest.php" name="tests/Standards/AbstractSniffUnitTest.php" />
</filelist>
Expand Down Expand Up @@ -2025,6 +2033,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/File/IsReferenceTest.inc" name="tests/Core/File/IsReferenceTest.inc" />
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.php" name="tests/Core/Filters/Filter/AcceptTest.php" />
<install as="CodeSniffer/Core/Filters/Filter/AcceptTest.xml" name="tests/Core/Filters/Filter/AcceptTest.xml" />
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php" />
<install as="CodeSniffer/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" name="tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc" />
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
<install as="CodeSniffer/Standards/AbstractSniffUnitTest.php" name="tests/Standards/AbstractSniffUnitTest.php" />
<ignore name="bin/phpcs.bat" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ public function register()
$this->ignoreTokens[T_CLOSE_SQUARE_BRACKET] = T_CLOSE_SQUARE_BRACKET;
$this->ignoreTokens[T_CLOSE_SHORT_ARRAY] = T_CLOSE_SHORT_ARRAY;

$this->ignoreTokens[T_ANON_CLASS] = T_ANON_CLASS;
$this->ignoreTokens[T_USE] = T_USE;
$this->ignoreTokens[T_DECLARE] = T_DECLARE;
$this->ignoreTokens[T_THROW] = T_THROW;
Expand Down
19 changes: 18 additions & 1 deletion src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,23 @@ protected function processAdditional()
echo "\t* token $i on line $line changed from T_CLASS to T_ANON_CLASS".PHP_EOL;
}

if ($this->tokens[$x]['code'] === T_OPEN_PARENTHESIS
&& isset($this->tokens[$x]['parenthesis_closer']) === true
) {
$closer = $this->tokens[$x]['parenthesis_closer'];

$this->tokens[$i]['parenthesis_opener'] = $x;
$this->tokens[$i]['parenthesis_closer'] = $closer;
$this->tokens[$i]['parenthesis_owner'] = $i;
$this->tokens[$x]['parenthesis_owner'] = $i;
$this->tokens[$closer]['parenthesis_owner'] = $i;

if (PHP_CODESNIFFER_VERBOSITY > 1) {
$line = $this->tokens[$i]['line'];
echo "\t\t* added parenthesis keys to T_ANON_CLASS token $i on line $line".PHP_EOL;
}
}

for ($x = ($this->tokens[$i]['scope_opener'] + 1); $x < $this->tokens[$i]['scope_closer']; $x++) {
if (isset($this->tokens[$x]['conditions'][$i]) === false) {
continue;
Expand All @@ -1570,7 +1587,7 @@ protected function processAdditional()
echo "\t\t* cleaned $x ($type) *".PHP_EOL;
}
}
}
}//end if

continue;
} else if ($this->tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) {
Expand Down
25 changes: 13 additions & 12 deletions src/Util/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,18 +344,19 @@ final class Tokens
* @var array<int, int>
*/
public static $parenthesisOpeners = [
T_ARRAY => T_ARRAY,
T_LIST => T_LIST,
T_FUNCTION => T_FUNCTION,
T_CLOSURE => T_CLOSURE,
T_WHILE => T_WHILE,
T_FOR => T_FOR,
T_FOREACH => T_FOREACH,
T_SWITCH => T_SWITCH,
T_IF => T_IF,
T_ELSEIF => T_ELSEIF,
T_CATCH => T_CATCH,
T_DECLARE => T_DECLARE,
T_ARRAY => T_ARRAY,
T_LIST => T_LIST,
T_FUNCTION => T_FUNCTION,
T_CLOSURE => T_CLOSURE,
T_ANON_CLASS => T_ANON_CLASS,
T_WHILE => T_WHILE,
T_FOR => T_FOR,
T_FOREACH => T_FOREACH,
T_SWITCH => T_SWITCH,
T_IF => T_IF,
T_ELSEIF => T_ELSEIF,
T_CATCH => T_CATCH,
T_DECLARE => T_DECLARE,
];

/**
Expand Down
19 changes: 19 additions & 0 deletions tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/* testNoParentheses */
$anonClass = new class {
function __construct() {}
};

/* testNoParenthesesAndEmptyTokens */
$anonClass = new class // phpcs:ignore Standard.Cat
{
function __construct() {}
};

/* testWithParentheses */
$anonClass = new class() {};

/* testWithParenthesesAndEmptyTokens */
$anonClass = new class /*comment */
() {};
144 changes: 144 additions & 0 deletions tests/Core/Tokenizer/PHP/T_AnonClassParenthesisOwnerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
/**
* Tests the adding of the "parenthesis" keys to an anonymous class token.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Tokenizer\PHP;

use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;

class T_AnonClassParenthesisOwnerTest extends AbstractMethodUnitTest
{


/**
* Test that anonymous class tokens without parenthesis do not get assigned a parenthesis owner.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataAnonClassNoParentheses
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testAnonClassNoParentheses($testMarker)
{
$tokens = self::$phpcsFile->getTokens();

$anonClass = $this->getTargetToken($testMarker, T_ANON_CLASS);
$this->assertFalse(array_key_exists('parenthesis_owner', $tokens[$anonClass]));
$this->assertFalse(array_key_exists('parenthesis_opener', $tokens[$anonClass]));
$this->assertFalse(array_key_exists('parenthesis_closer', $tokens[$anonClass]));

}//end testAnonClassNoParentheses()


/**
* Test that the next open/close parenthesis after an anonymous class without parenthesis
* do not get assigned the anonymous class as a parenthesis owner.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataAnonClassNoParentheses
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testAnonClassNoParenthesesNextOpenClose($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$function = $this->getTargetToken($testMarker, T_FUNCTION);

$opener = $this->getTargetToken($testMarker, T_OPEN_PARENTHESIS);
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]));
$this->assertSame($function, $tokens[$opener]['parenthesis_owner']);

$closer = $this->getTargetToken($testMarker, T_CLOSE_PARENTHESIS);
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]));
$this->assertSame($function, $tokens[$closer]['parenthesis_owner']);

}//end testAnonClassNoParenthesesNextOpenClose()


/**
* Data provider.
*
* @see testAnonClassNoParentheses()
* @see testAnonClassNoParenthesesNextOpenClose()
*
* @return array
*/
public function dataAnonClassNoParentheses()
{
return [
['/* testNoParentheses */'],
['/* testNoParenthesesAndEmptyTokens */'],
];

}//end dataAnonClassNoParentheses()


/**
* Test that anonymous class tokens with parenthesis get assigned a parenthesis owner,
* opener and closer; and that the opener/closer get the anonymous class assigned as owner.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataAnonClassWithParentheses
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testAnonClassWithParentheses($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$anonClass = $this->getTargetToken($testMarker, T_ANON_CLASS);
$opener = $this->getTargetToken($testMarker, T_OPEN_PARENTHESIS);
$closer = $this->getTargetToken($testMarker, T_CLOSE_PARENTHESIS);

$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$anonClass]));
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$anonClass]));
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$anonClass]));
$this->assertSame($anonClass, $tokens[$anonClass]['parenthesis_owner']);
$this->assertSame($opener, $tokens[$anonClass]['parenthesis_opener']);
$this->assertSame($closer, $tokens[$anonClass]['parenthesis_closer']);

$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]));
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$opener]));
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$opener]));
$this->assertSame($anonClass, $tokens[$opener]['parenthesis_owner']);
$this->assertSame($opener, $tokens[$opener]['parenthesis_opener']);
$this->assertSame($closer, $tokens[$opener]['parenthesis_closer']);

$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]));
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$closer]));
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$closer]));
$this->assertSame($anonClass, $tokens[$closer]['parenthesis_owner']);
$this->assertSame($opener, $tokens[$closer]['parenthesis_opener']);
$this->assertSame($closer, $tokens[$closer]['parenthesis_closer']);

}//end testAnonClassWithParentheses()


/**
* Data provider.
*
* @see testAnonClassWithParentheses()
*
* @return array
*/
public function dataAnonClassWithParentheses()
{
return [
['/* testWithParentheses */'],
['/* testWithParenthesesAndEmptyTokens */'],
];

}//end dataAnonClassWithParentheses()


}//end class

0 comments on commit baaf896

Please sign in to comment.