Skip to content

Commit a542524

Browse files
committed
BCFile::getDeclarationName(): sync with PHPCS 4.0 / stop accepting tokens for non-named structures
The `[BC]File::getDeclarationName()` method - for historic reasons - accepted the `T_CLOSURE` and `T_ANON_CLASS` tokens, even though these structures will never have a name, and returned `null` for those tokens. This commit changes the `BCFile::getDeclarationName()` method to no longer accept those tokens and throw an exception if they are passed to the method instead. As a secondary change, when the name of a valid structure cannot be determined, the method will now no longer return `null`, but will return an empty string. This normalizes the return type of the method to always return a string (or throw an exception). Includes updated unit tests to match. This change mirrors the upstream change made to the `File::getDeclarationName()` method in PHPCS 4.0. Note: this change is NOT mirrored in the `ObjectDeclarations::getName()` method, as changing it there would constitute a breaking change for PHPCSUtils, so that change needs to wait until PHPCSUtils 2.0. Ref: PHPCSStandards/PHP_CodeSniffer 1007
1 parent 8471a1c commit a542524

File tree

10 files changed

+142
-67
lines changed

10 files changed

+142
-67
lines changed

PHPCSUtils/BackCompat/BCFile.php

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ final class BCFile
7676
*
7777
* Changelog for the PHPCS native function:
7878
* - Introduced in PHPCS 0.0.5.
79-
* - The upstream method has received no significant updates since PHPCS 3.13.0.
79+
* - PHPCS 4.0: The method no longer accepts `T_CLOSURE` and `T_ANON_CLASS` tokens.
80+
* - PHPCS 4.0: The method will now always return a string.
8081
*
8182
* @see \PHP_CodeSniffer\Files\File::getDeclarationName() Original source.
8283
* @see \PHPCSUtils\Utils\ObjectDeclarations::getName() PHPCSUtils native improved version.
@@ -88,17 +89,53 @@ final class BCFile
8889
* which declared the class, interface,
8990
* trait, enum or function.
9091
*
91-
* @return string|null The name of the class, interface, trait, enum, or function;
92-
* or `NULL` if the function or class is anonymous or
93-
* in case of a parse error/live coding.
92+
* @return string The name of the class, interface, trait, or function or an empty string
93+
* if the name could not be determined (live coding).
9494
*
9595
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
96-
* `T_FUNCTION`, `T_CLASS`, `T_ANON_CLASS`,
97-
* `T_CLOSURE`, `T_TRAIT`, `T_ENUM` or `T_INTERFACE`.
96+
* `T_FUNCTION`, `T_CLASS`, `T_TRAIT`, `T_ENUM`, or `T_INTERFACE`.
9897
*/
9998
public static function getDeclarationName(File $phpcsFile, $stackPtr)
10099
{
101-
return $phpcsFile->getDeclarationName($stackPtr);
100+
$tokens = $phpcsFile->getTokens();
101+
102+
$tokenCode = $tokens[$stackPtr]['code'];
103+
104+
if ($tokenCode !== T_FUNCTION
105+
&& $tokenCode !== T_CLASS
106+
&& $tokenCode !== T_INTERFACE
107+
&& $tokenCode !== T_TRAIT
108+
&& $tokenCode !== T_ENUM
109+
) {
110+
throw new RuntimeException('Token type "' . $tokens[$stackPtr]['type'] . '" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM');
111+
}
112+
113+
if ($tokenCode === T_FUNCTION
114+
&& strtolower($tokens[$stackPtr]['content']) !== 'function'
115+
) {
116+
// This is a function declared without the "function" keyword.
117+
// So this token is the function name.
118+
return $tokens[$stackPtr]['content'];
119+
}
120+
121+
$stopPoint = $phpcsFile->numTokens;
122+
if (isset($tokens[$stackPtr]['parenthesis_opener']) === true) {
123+
// For functions, stop searching at the parenthesis opener.
124+
$stopPoint = $tokens[$stackPtr]['parenthesis_opener'];
125+
} elseif (isset($tokens[$stackPtr]['scope_opener']) === true) {
126+
// For OO tokens, stop searching at the open curly.
127+
$stopPoint = $tokens[$stackPtr]['scope_opener'];
128+
}
129+
130+
$content = '';
131+
for ($i = $stackPtr; $i < $stopPoint; $i++) {
132+
if ($tokens[$i]['code'] === T_STRING) {
133+
$content = $tokens[$i]['content'];
134+
break;
135+
}
136+
}
137+
138+
return $content;
102139
}
103140

104141
/**

PHPCSUtils/Utils/ObjectDeclarations.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ final class ObjectDeclarations
4646
* being extended/interface being implemented.
4747
* Using this version of the utility method, either the complete name (invalid or not) will
4848
* be returned or `null` in case of no name (parse error).
49+
* - The PHPCS 4.0 change to no longer accept tokens for anonymous structures (T_CLOSURE/T_ANON_CLASS)
50+
* has not been applied to this method (yet). This will change in PHPCSUtils 2.0.
51+
* - The PHPCS 4.0 change to normalize the return type to `string` and no longer return `null`
52+
* has not been applied to this method (yet). This will change in PHPCSUtils 2.0.
4953
*
5054
* @see \PHP_CodeSniffer\Files\File::getDeclarationName() Original source.
5155
* @see \PHPCSUtils\BackCompat\BCFile::getDeclarationName() Cross-version compatible version of the original.

Tests/BackCompat/BCFile/GetDeclarationNameJSTest.php

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace PHPCSUtils\Tests\BackCompat\BCFile;
1212

13+
use PHP_CodeSniffer\Util\Tokens;
1314
use PHPCSUtils\BackCompat\BCFile;
1415
use PHPCSUtils\Tests\PolyfilledTestCase;
1516

@@ -35,43 +36,38 @@ class GetDeclarationNameJSTest extends PolyfilledTestCase
3536
/**
3637
* Test receiving an expected exception when a non-supported token is passed.
3738
*
38-
* @return void
39-
*/
40-
public function testInvalidTokenPassed()
41-
{
42-
$this->expectPhpcsException('Token type "T_STRING" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM');
43-
44-
$target = $this->getTargetToken('/* testInvalidTokenPassed */', \T_STRING);
45-
BCFile::getDeclarationName(self::$phpcsFile, $target);
46-
}
47-
48-
/**
49-
* Test receiving "null" when passed an anonymous construct or in case of a parse error.
50-
*
51-
* @dataProvider dataGetDeclarationNameNull
39+
* @dataProvider dataInvalidTokenPassed
5240
*
5341
* @param string $testMarker The comment which prefaces the target token in the test file.
5442
* @param int|string $targetType Token type of the token to get as stackPtr.
5543
*
5644
* @return void
5745
*/
58-
public function testGetDeclarationNameNull($testMarker, $targetType)
46+
public function testInvalidTokenPassed($testMarker, $targetType)
5947
{
48+
$tokenName = Tokens::tokenName($targetType);
49+
$this->expectPhpcsException(
50+
'Token type "' . $tokenName . '" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM'
51+
);
52+
6053
$target = $this->getTargetToken($testMarker, $targetType);
61-
$result = BCFile::getDeclarationName(self::$phpcsFile, $target);
62-
$this->assertNull($result);
54+
BCFile::getDeclarationName(self::$phpcsFile, $target);
6355
}
6456

6557
/**
6658
* Data provider.
6759
*
68-
* @see GetDeclarationNameTest::testGetDeclarationNameNull()
60+
* @see testInvalidTokenPassed() For the array format.
6961
*
7062
* @return array<string, array<string, int|string>>
7163
*/
72-
public static function dataGetDeclarationNameNull()
64+
public static function dataInvalidTokenPassed()
7365
{
7466
return [
67+
'unsupported token T_STRING' => [
68+
'testMarker' => '/* testInvalidTokenPassed */',
69+
'targetType' => \T_STRING,
70+
],
7571
'closure' => [
7672
'testMarker' => '/* testClosure */',
7773
'targetType' => \T_CLOSURE,

Tests/BackCompat/BCFile/GetDeclarationNameParseError1Test.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,30 @@ class GetDeclarationNameParseError1Test extends PolyfilledTestCase
2626
{
2727

2828
/**
29-
* Test receiving "null" in case of a parse error.
29+
* Test receiving an empty string in case of a parse error.
3030
*
31-
* @dataProvider dataGetDeclarationNameNull
31+
* @dataProvider dataGetDeclarationName
3232
*
3333
* @param string $testMarker The comment which prefaces the target token in the test file.
3434
* @param int|string $targetType Token type of the token to get as stackPtr.
3535
*
3636
* @return void
3737
*/
38-
public function testGetDeclarationNameNull($testMarker, $targetType)
38+
public function testGetDeclarationName($testMarker, $targetType)
3939
{
4040
$target = $this->getTargetToken($testMarker, $targetType);
4141
$result = BCFile::getDeclarationName(self::$phpcsFile, $target);
42-
$this->assertNull($result);
42+
$this->assertSame('', $result);
4343
}
4444

4545
/**
4646
* Data provider.
4747
*
48-
* @see testGetDeclarationNameNull() For the array format.
48+
* @see testGetDeclarationName() For the array format.
4949
*
5050
* @return array<string, array<string, int|string>>
5151
*/
52-
public static function dataGetDeclarationNameNull()
52+
public static function dataGetDeclarationName()
5353
{
5454
return [
5555
'unfinished function/live coding' => [

Tests/BackCompat/BCFile/GetDeclarationNameParseError2Test.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,30 @@ class GetDeclarationNameParseError2Test extends PolyfilledTestCase
2626
{
2727

2828
/**
29-
* Test receiving "null" in case of a parse error.
29+
* Test receiving an empty string in case of a parse error.
3030
*
31-
* @dataProvider dataGetDeclarationNameNull
31+
* @dataProvider dataGetDeclarationName
3232
*
3333
* @param string $testMarker The comment which prefaces the target token in the test file.
3434
* @param int|string $targetType Token type of the token to get as stackPtr.
3535
*
3636
* @return void
3737
*/
38-
public function testGetDeclarationNameNull($testMarker, $targetType)
38+
public function testGetDeclarationName($testMarker, $targetType)
3939
{
4040
$target = $this->getTargetToken($testMarker, $targetType);
4141
$result = BCFile::getDeclarationName(self::$phpcsFile, $target);
42-
$this->assertNull($result);
42+
$this->assertSame('', $result);
4343
}
4444

4545
/**
4646
* Data provider.
4747
*
48-
* @see testGetDeclarationNameNull() For the array format.
48+
* @see testGetDeclarationName() For the array format.
4949
*
5050
* @return array<string, array<string, int|string>>
5151
*/
52-
public static function dataGetDeclarationNameNull()
52+
public static function dataGetDeclarationName()
5353
{
5454
return [
5555
'unfinished closure/live coding' => [

Tests/BackCompat/BCFile/GetDeclarationNameTest.php

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace PHPCSUtils\Tests\BackCompat\BCFile;
1212

13+
use PHP_CodeSniffer\Util\Tokens;
1314
use PHPCSUtils\BackCompat\BCFile;
1415
use PHPCSUtils\Tests\PolyfilledTestCase;
1516

@@ -28,43 +29,38 @@ class GetDeclarationNameTest extends PolyfilledTestCase
2829
/**
2930
* Test receiving an expected exception when a non-supported token is passed.
3031
*
31-
* @return void
32-
*/
33-
public function testInvalidTokenPassed()
34-
{
35-
$this->expectPhpcsException('Token type "T_STRING" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM');
36-
37-
$target = $this->getTargetToken('/* testInvalidTokenPassed */', \T_STRING);
38-
BCFile::getDeclarationName(self::$phpcsFile, $target);
39-
}
40-
41-
/**
42-
* Test receiving "null" when passed an anonymous construct or in case of a parse error.
43-
*
44-
* @dataProvider dataGetDeclarationNameNull
32+
* @dataProvider dataInvalidTokenPassed
4533
*
4634
* @param string $testMarker The comment which prefaces the target token in the test file.
4735
* @param int|string $targetType Token type of the token to get as stackPtr.
4836
*
4937
* @return void
5038
*/
51-
public function testGetDeclarationNameNull($testMarker, $targetType)
39+
public function testInvalidTokenPassed($testMarker, $targetType)
5240
{
41+
$tokenName = Tokens::tokenName($targetType);
42+
$this->expectPhpcsException(
43+
'Token type "' . $tokenName . '" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM'
44+
);
45+
5346
$target = $this->getTargetToken($testMarker, $targetType);
54-
$result = BCFile::getDeclarationName(self::$phpcsFile, $target);
55-
$this->assertNull($result);
47+
BCFile::getDeclarationName(self::$phpcsFile, $target);
5648
}
5749

5850
/**
5951
* Data provider.
6052
*
61-
* @see testGetDeclarationNameNull() For the array format.
53+
* @see testInvalidTokenPassed() For the array format.
6254
*
6355
* @return array<string, array<string, int|string>>
6456
*/
65-
public static function dataGetDeclarationNameNull()
57+
public static function dataInvalidTokenPassed()
6658
{
6759
return [
60+
'unsupported token T_STRING' => [
61+
'testMarker' => '/* testInvalidTokenPassed */',
62+
'targetType' => \T_STRING,
63+
],
6864
'closure' => [
6965
'testMarker' => '/* testClosure */',
7066
'targetType' => \T_CLOSURE,

Tests/Utils/ObjectDeclarations/GetNameJSTest.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static function setUpTestFile()
5252
*
5353
* @return void
5454
*/
55-
public function testInvalidTokenPassed()
55+
public function testTrulyInvalidTokenPassed()
5656
{
5757
$this->expectException('PHPCSUtils\Exceptions\UnexpectedTokenType');
5858
$this->expectExceptionMessage(
@@ -66,22 +66,40 @@ public function testInvalidTokenPassed()
6666
/**
6767
* Test receiving "null" when passed an anonymous construct or in case of a parse error.
6868
*
69+
* Note: the upstream and the BCFile method no longer returns `null`, but throws an exception.
70+
* For PHPCSUtils, this change needs to wait for the next major.
71+
*
6972
* {@internal Method name not adjusted as otherwise it wouldn't overload the parent method.}
7073
*
71-
* @dataProvider dataGetDeclarationNameNull
74+
* @dataProvider dataInvalidTokenPassed
7275
*
7376
* @param string $testMarker The comment which prefaces the target token in the test file.
7477
* @param int|string $targetType Token type of the token to get as stackPtr.
7578
*
7679
* @return void
7780
*/
78-
public function testGetDeclarationNameNull($testMarker, $targetType)
81+
public function testInvalidTokenPassed($testMarker, $targetType)
7982
{
8083
$target = $this->getTargetToken($testMarker, $targetType);
8184
$result = ObjectDeclarations::getName(self::$phpcsFile, $target);
8285
$this->assertNull($result);
8386
}
8487

88+
/**
89+
* Data provider.
90+
*
91+
* @see testInvalidTokenPassed() For the array format.
92+
*
93+
* @return array<string, array<string, int|string>>
94+
*/
95+
public static function dataInvalidTokenPassed()
96+
{
97+
$data = parent::dataInvalidTokenPassed();
98+
unset($data['unsupported token T_STRING']);
99+
100+
return $data;
101+
}
102+
85103
/**
86104
* Test retrieving the name of a function or OO structure.
87105
*

Tests/Utils/ObjectDeclarations/GetNameParseError1Test.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@ public static function setUpTestFile()
5050
/**
5151
* Test receiving "null" in case of a parse error.
5252
*
53-
* @dataProvider dataGetDeclarationNameNull
53+
* Note: the upstream and the BCFile method no longer returns `null`, but an empty string.
54+
* For PHPCSUtils, this change needs to wait for the next major.
55+
*
56+
* @dataProvider dataGetDeclarationName
5457
*
5558
* @param string $testMarker The comment which prefaces the target token in the test file.
5659
* @param int|string $targetType Token type of the token to get as stackPtr.
5760
*
5861
* @return void
5962
*/
60-
public function testGetDeclarationNameNull($testMarker, $targetType)
63+
public function testGetDeclarationName($testMarker, $targetType)
6164
{
6265
$target = $this->getTargetToken($testMarker, $targetType);
6366
$result = ObjectDeclarations::getName(self::$phpcsFile, $target);

Tests/Utils/ObjectDeclarations/GetNameParseError2Test.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,17 @@ public static function setUpTestFile()
5050
/**
5151
* Test receiving "null" in case of a parse error.
5252
*
53-
* @dataProvider dataGetDeclarationNameNull
53+
* Note: the upstream and the BCFile method no longer returns `null`, but an empty string.
54+
* For PHPCSUtils, this change needs to wait for the next major.
55+
*
56+
* @dataProvider dataGetDeclarationName
5457
*
5558
* @param string $testMarker The comment which prefaces the target token in the test file.
5659
* @param int|string $targetType Token type of the token to get as stackPtr.
5760
*
5861
* @return void
5962
*/
60-
public function testGetDeclarationNameNull($testMarker, $targetType)
63+
public function testGetDeclarationName($testMarker, $targetType)
6164
{
6265
$target = $this->getTargetToken($testMarker, $targetType);
6366
$result = ObjectDeclarations::getName(self::$phpcsFile, $target);

0 commit comments

Comments
 (0)