Skip to content

Commit

Permalink
BCFile::getDeclarationName(): sync with upstream
Browse files Browse the repository at this point in the history
The upstream File::getDeclarationName() method is slated to receive a bug fix in the PHPCS 3.12.0 release to prevent incorrect results for unfinished closures with typed parameters.

The PHPCSUtils native `ObjectDeclarations::getName()` method already handled this correct.

This commit polyfills the upstream method via the BCFile class and syncs the tests with the upstream changes.

Ref: PHPCSStandards/PHP_CodeSniffer 816
  • Loading branch information
jrfnl committed Feb 13, 2025
1 parent 8ef592a commit eb8a591
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 10 deletions.
44 changes: 42 additions & 2 deletions PHPCSUtils/BackCompat/BCFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ final class BCFile
*
* Changelog for the PHPCS native function:
* - Introduced in PHPCS 0.0.5.
* - The upstream method has received no significant updates since PHPCS 3.10.1.
* - PHPCS 3.12.0: hardening to handle unfinished closure better.
*
* @see \PHP_CodeSniffer\Files\File::getDeclarationName() Original source.
* @see \PHPCSUtils\Utils\ObjectDeclarations::getName() PHPCSUtils native improved version.
Expand All @@ -98,7 +98,47 @@ final class BCFile
*/
public static function getDeclarationName(File $phpcsFile, $stackPtr)
{
return $phpcsFile->getDeclarationName($stackPtr);
$tokens = $phpcsFile->getTokens();

$tokenCode = $tokens[$stackPtr]['code'];

if ($tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) {
return null;
}

if ($tokenCode !== T_FUNCTION
&& $tokenCode !== T_CLASS
&& $tokenCode !== T_INTERFACE
&& $tokenCode !== T_TRAIT
&& $tokenCode !== T_ENUM
) {
throw new RuntimeException('Token type "' . $tokens[$stackPtr]['type'] . '" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM');
}

if ($tokenCode === T_FUNCTION
&& strtolower($tokens[$stackPtr]['content']) !== 'function'
) {
// This is a function declared without the "function" keyword.
// So this token is the function name.
return $tokens[$stackPtr]['content'];
}

$stopPoint = $phpcsFile->numTokens;
if ($tokenCode === T_FUNCTION && isset($tokens[$stackPtr]['parenthesis_opener']) === true) {
$stopPoint = $tokens[$stackPtr]['parenthesis_opener'];
} elseif (isset($tokens[$stackPtr]['scope_opener']) === true) {
$stopPoint = $tokens[$stackPtr]['scope_opener'];
}

$content = null;
for ($i = $stackPtr; $i < $stopPoint; $i++) {
if ($tokens[$i]['code'] === T_STRING) {
$content = $tokens[$i]['content'];
break;
}
}

return $content;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions Tests/BackCompat/BCFile/GetDeclarationNameParseError1Test.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

/* testLiveCoding */
// Intentional parse error. This must be the only test in the file.
function // Comment.
61 changes: 61 additions & 0 deletions Tests/BackCompat/BCFile/GetDeclarationNameParseError1Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Tests\BackCompat\BCFile;

use PHPCSUtils\BackCompat\BCFile;
use PHPCSUtils\Tests\PolyfilledTestCase;

/**
* Tests for the \PHPCSUtils\BackCompat\BCFile::getDeclarationName() method.
*
* @covers \PHPCSUtils\BackCompat\BCFile::getDeclarationName
*
* @group objectdeclarations
*
* @since 1.0.0
*/
class GetDeclarationNameParseError1Test extends PolyfilledTestCase
{

/**
* Test receiving "null" in case of a parse error.
*
* @dataProvider dataGetDeclarationNameNull
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $targetType Token type of the token to get as stackPtr.
*
* @return void
*/
public function testGetDeclarationNameNull($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$result = BCFile::getDeclarationName(self::$phpcsFile, $target);
$this->assertNull($result);
}

/**
* Data provider.
*
* @see testGetDeclarationNameNull() For the array format.
*
* @return array<string, array<string, int|string>>
*/
public static function dataGetDeclarationNameNull()
{
return [
'unfinished function/live coding' => [
'testMarker' => '/* testLiveCoding */',
'targetType' => \T_FUNCTION,
],
];
}
}
6 changes: 6 additions & 0 deletions Tests/BackCompat/BCFile/GetDeclarationNameParseError2Test.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

/* testLiveCoding */
// Intentional parse error/live coding. This must be the only test in the file.
// Safeguarding that the utility method does not confuse the `string` type with a function name.
$closure = function (string $param) use ($var
61 changes: 61 additions & 0 deletions Tests/BackCompat/BCFile/GetDeclarationNameParseError2Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Tests\BackCompat\BCFile;

use PHPCSUtils\BackCompat\BCFile;
use PHPCSUtils\Tests\PolyfilledTestCase;

/**
* Tests for the \PHPCSUtils\BackCompat\BCFile::getDeclarationName() method.
*
* @covers \PHPCSUtils\BackCompat\BCFile::getDeclarationName
*
* @group objectdeclarations
*
* @since 1.1.0
*/
class GetDeclarationNameParseError2Test extends PolyfilledTestCase
{

/**
* Test receiving "null" in case of a parse error.
*
* @dataProvider dataGetDeclarationNameNull
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $targetType Token type of the token to get as stackPtr.
*
* @return void
*/
public function testGetDeclarationNameNull($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$result = BCFile::getDeclarationName(self::$phpcsFile, $target);
$this->assertNull($result);
}

/**
* Data provider.
*
* @see testGetDeclarationNameNull() For the array format.
*
* @return array<string, array<string, int|string>>
*/
public static function dataGetDeclarationNameNull()
{
return [
'unfinished closure/live coding' => [
'testMarker' => '/* testLiveCoding */',
'targetType' => \T_FUNCTION,
],
];
}
}
4 changes: 0 additions & 4 deletions Tests/BackCompat/BCFile/GetDeclarationNameTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,3 @@ function &self() {}

/* testFunctionReturnByRefWithReservedKeywordStatic */
function &static() {}

/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
function // Comment.
4 changes: 0 additions & 4 deletions Tests/BackCompat/BCFile/GetDeclarationNameTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ public static function dataGetDeclarationNameNull()
'testMarker' => '/* testAnonClassExtendsWithoutParens */',
'targetType' => \T_ANON_CLASS,
],
'live-coding' => [
'testMarker' => '/* testLiveCoding */',
'targetType' => \T_FUNCTION,
],
];
}

Expand Down
66 changes: 66 additions & 0 deletions Tests/Utils/ObjectDeclarations/GetNameParseError1Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Tests\Utils\ObjectDeclarations;

use PHPCSUtils\Tests\BackCompat\BCFile\GetDeclarationNameParseError1Test as BCFile_GetDeclarationNameParseError1Test;
use PHPCSUtils\Utils\ObjectDeclarations;

/**
* Tests for the \PHPCSUtils\Utils\ObjectDeclarations::getName() method.
*
* @covers \PHPCSUtils\Utils\ObjectDeclarations::getName
*
* @group objectdeclarations
*
* @since 1.0.0
*/
final class GetNameParseError1Test extends BCFile_GetDeclarationNameParseError1Test
{

/**
* Full path to the test case file associated with this test class.
*
* @var string
*/
protected static $caseFile = '';

/**
* Initialize PHPCS & tokenize the test case file.
*
* Overloaded to re-use the `$caseFile` from the BCFile test.
*
* @beforeClass
*
* @return void
*/
public static function setUpTestFile()
{
self::$caseFile = \dirname(\dirname(__DIR__)) . '/BackCompat/BCFile/GetDeclarationNameParseError1Test.inc';
parent::setUpTestFile();
}

/**
* Test receiving "null" in case of a parse error.
*
* @dataProvider dataGetDeclarationNameNull
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $targetType Token type of the token to get as stackPtr.
*
* @return void
*/
public function testGetDeclarationNameNull($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$result = ObjectDeclarations::getName(self::$phpcsFile, $target);
$this->assertNull($result);
}
}
66 changes: 66 additions & 0 deletions Tests/Utils/ObjectDeclarations/GetNameParseError2Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
*
* @package PHPCSUtils
* @copyright 2019-2020 PHPCSUtils Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSUtils
*/

namespace PHPCSUtils\Tests\Utils\ObjectDeclarations;

use PHPCSUtils\Tests\BackCompat\BCFile\GetDeclarationNameParseError2Test as BCFile_GetDeclarationNameParseError2Test;
use PHPCSUtils\Utils\ObjectDeclarations;

/**
* Tests for the \PHPCSUtils\Utils\ObjectDeclarations::getName() method.
*
* @covers \PHPCSUtils\Utils\ObjectDeclarations::getName
*
* @group objectdeclarations
*
* @since 1.0.0
*/
final class GetNameParseError2Test extends BCFile_GetDeclarationNameParseError2Test
{

/**
* Full path to the test case file associated with this test class.
*
* @var string
*/
protected static $caseFile = '';

/**
* Initialize PHPCS & tokenize the test case file.
*
* Overloaded to re-use the `$caseFile` from the BCFile test.
*
* @beforeClass
*
* @return void
*/
public static function setUpTestFile()
{
self::$caseFile = \dirname(\dirname(__DIR__)) . '/BackCompat/BCFile/GetDeclarationNameParseError2Test.inc';
parent::setUpTestFile();
}

/**
* Test receiving "null" in case of a parse error.
*
* @dataProvider dataGetDeclarationNameNull
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $targetType Token type of the token to get as stackPtr.
*
* @return void
*/
public function testGetDeclarationNameNull($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$result = ObjectDeclarations::getName(self::$phpcsFile, $target);
$this->assertNull($result);
}
}

0 comments on commit eb8a591

Please sign in to comment.