Skip to content

Commit

Permalink
PHP 8.2 | BCFile::getMethodParameters(): sync with upstream / add sup…
Browse files Browse the repository at this point in the history
…port for true pseudotype

As pulled in upstream PR squizlabs/PHP_CodeSniffer3662 / PHPCSStandards/PHP_CodeSniffer 49.

Includes unit tests.
Includes moving some tests to allow the tests to still work as expected.

Support for the `true` type was previously already added to the `FunctionDeclarations::getParameters()` method in PR 368.

Refs:
* squizlabs/PHP_CodeSniffer 3662
* PHPCSStandards/PHP_CodeSniffer 49
* PHPCSStandards/PHPCSUtils 368
  • Loading branch information
jrfnl committed Dec 8, 2023
1 parent f6ec382 commit d1ddb2c
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 205 deletions.
1 change: 1 addition & 0 deletions PHPCSUtils/BackCompat/BCFile.php
Expand Up @@ -362,6 +362,7 @@ public static function getMethodParameters(File $phpcsFile, $stackPtr)
case T_TYPE_UNION:
case T_TYPE_INTERSECTION:
case T_FALSE:
case T_TRUE:
case T_NULL:
// Part of a type hint or default value.
if ($defaultStart === null) {
Expand Down
7 changes: 0 additions & 7 deletions PHPCSUtils/Utils/FunctionDeclarations.php
Expand Up @@ -360,7 +360,6 @@ public static function getProperties(File $phpcsFile, $stackPtr)
* - More efficient and more stable looping of the default value.
* - Clearer exception message when a non-closure use token was passed to the function.
* - Support for PHP 8.0 identifier name tokens in parameter types, cross-version PHP & PHPCS.
* - Support for the PHP 8.2 `true` type.
* - The results of this function call are cached during a PHPCS run for faster response times.
*
* @see \PHP_CodeSniffer\Files\File::getMethodParameters() Original source.
Expand Down Expand Up @@ -440,12 +439,6 @@ public static function getParameters(File $phpcsFile, $stackPtr)

$parameterTypeTokens = Collections::parameterTypeTokens();

/*
* BC PHPCS < 3.x.x: The union type separator is not (yet) retokenized correctly
* for union types containing the `true` type.
*/
$parameterTypeTokens[\T_BITWISE_OR] = \T_BITWISE_OR;

for ($i = $paramStart; $i <= $closer; $i++) {
if (isset($parameterTypeTokens[$tokens[$i]['code']]) === true
/*
Expand Down
11 changes: 9 additions & 2 deletions Tests/BackCompat/BCFile/GetMethodParametersTest.inc
Expand Up @@ -159,11 +159,11 @@ function unionTypesAllPseudoTypes(false|mixed|self|parent|iterable|Resource $var
$closure = function (?int|float $number) {};

/* testPHP8PseudoTypeNull */
// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeNull(null $var = null) {}

/* testPHP8PseudoTypeFalse */
// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeFalse(false $var = false) {}

/* testPHP8PseudoTypeFalseAndBool */
Expand Down Expand Up @@ -267,6 +267,13 @@ $closure = function (string&int $numeric_string) {};
// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
$closure = function (?Foo&Bar $object) {};

/* testPHP82PseudoTypeTrue */
function pseudoTypeTrue(?true $var = true) {}

/* testPHP82PseudoTypeFalseAndTrue */
// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method.
function pseudoTypeFalseAndTrue(true|false $var = true) {}

/* testPHP81NewInInitializers */
function newInInitializers(
TypeA $new = new TypeA(self::CONST_VALUE),
Expand Down
60 changes: 60 additions & 0 deletions Tests/BackCompat/BCFile/GetMethodParametersTest.php
Expand Up @@ -2436,6 +2436,66 @@ public function testPHP81NullableIntersectionTypes()
$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Verify recognition of PHP 8.2 stand-alone `true` type.
*
* @return void
*/
public function testPHP82PseudoTypeTrue()
{
$expected = [];
$expected[0] = [
'token' => 7, // Offset from the T_FUNCTION token.
'name' => '$var',
'content' => '?true $var = true',
'default' => 'true',
'default_token' => 11, // Offset from the T_FUNCTION token.
'default_equal_token' => 9, // Offset from the T_FUNCTION token.
'has_attributes' => false,
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => '?true',
'type_hint_token' => 5, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 5, // Offset from the T_FUNCTION token.
'nullable_type' => true,
'comma_token' => false,
];

$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true.
*
* @return void
*/
public function testPHP82PseudoTypeFalseAndTrue()
{
$expected = [];
$expected[0] = [
'token' => 8, // Offset from the T_FUNCTION token.
'name' => '$var',
'content' => 'true|false $var = true',
'default' => 'true',
'default_token' => 12, // Offset from the T_FUNCTION token.
'default_equal_token' => 10, // Offset from the T_FUNCTION token.
'has_attributes' => false,
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => 'true|false',
'type_hint_token' => 4, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 6, // Offset from the T_FUNCTION token.
'nullable_type' => false,
'comma_token' => false,
];

$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Verify behaviour when the default value uses the "new" keyword, as is allowed per PHP 8.1.
*
Expand Down
8 changes: 0 additions & 8 deletions Tests/Utils/FunctionDeclarations/GetParametersDiffTest.inc

This file was deleted.

184 changes: 9 additions & 175 deletions Tests/Utils/FunctionDeclarations/GetParametersDiffTest.php
Expand Up @@ -10,9 +10,7 @@

namespace PHPCSUtils\Tests\Utils\FunctionDeclarations;

use PHPCSUtils\Internal\Cache;
use PHPCSUtils\TestUtils\UtilityMethodTestCase;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\FunctionDeclarations;

/**
Expand All @@ -31,191 +29,27 @@ final class GetParametersDiffTest extends UtilityMethodTestCase
{

/**
* Test passing a non-existent token pointer.
*
* @return void
*/
public function testNonExistentToken()
{
$this->expectPhpcsException('$stackPtr must be of type T_FUNCTION, T_CLOSURE or T_USE or an arrow function');

FunctionDeclarations::getParameters(self::$phpcsFile, 10000);
}

/**
* Verify recognition of PHP 8.2 stand-alone `true` type.
*
* @return void
*/
public function testPHP82PseudoTypeTrue()
{
$expected = [];
$expected[0] = [
'token' => 7, // Offset from the T_FUNCTION token.
'name' => '$var',
'content' => '?true $var = true',
'default' => 'true',
'default_token' => 11, // Offset from the T_FUNCTION token.
'default_equal_token' => 9, // Offset from the T_FUNCTION token.
'has_attributes' => false,
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => '?true',
'type_hint_token' => 5, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 5, // Offset from the T_FUNCTION token.
'nullable_type' => true,
'comma_token' => false,
];

$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Verify recognition of PHP 8.2 type declaration with (illegal) type false combined with type true.
*
* @return void
*/
public function testPHP82PseudoTypeFalseAndTrue()
{
$expected = [];
$expected[0] = [
'token' => 8, // Offset from the T_FUNCTION token.
'name' => '$var',
'content' => 'true|false $var = true',
'default' => 'true',
'default_token' => 12, // Offset from the T_FUNCTION token.
'default_equal_token' => 10, // Offset from the T_FUNCTION token.
'has_attributes' => false,
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => 'true|false',
'type_hint_token' => 4, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 6, // Offset from the T_FUNCTION token.
'nullable_type' => false,
'comma_token' => false,
];

$this->getMethodParametersTestHelper('/* ' . __FUNCTION__ . ' */', $expected);
}

/**
* Test helper.
* Initialize PHPCS & tokenize the test case file.
*
* @param string $marker The comment which preceeds the test.
* @param array $expected The expected function output.
* @param array $targetType Optional. The token type to search for after $marker.
* Defaults to the function/closure/arrow tokens.
* @beforeClass
*
* @return void
*/
protected function getMethodParametersTestHelper($marker, $expected, $targetType = [\T_FUNCTION, \T_CLOSURE, \T_FN])
public static function setUpTestFile()
{
$target = $this->getTargetToken($marker, $targetType);
$found = FunctionDeclarations::getParameters(self::$phpcsFile, $target);
$expected = $this->updateExpectedTokenPositions($target, $expected);

$this->assertSame($expected, $found);
self::$caseFile = \dirname(\dirname(__DIR__)) . '/DummyFile.inc';
parent::setUpTestFile();
}

/**
* Test helper to translate token offsets to absolute positions in an "expected" array.
*
* @param int $targetPtr The token pointer to the target token from which
* the offset is calculated.
* @param array<int, array<string, mixed>> $expected The expected function output containing offsets.
*
* @return array<int, array<string, mixed>>
*/
private function updateExpectedTokenPositions($targetPtr, $expected)
{
foreach ($expected as $key => $param) {
$expected[$key]['token'] += $targetPtr;

if ($param['reference_token'] !== false) {
$expected[$key]['reference_token'] += $targetPtr;
}
if ($param['variadic_token'] !== false) {
$expected[$key]['variadic_token'] += $targetPtr;
}
if ($param['type_hint_token'] !== false) {
$expected[$key]['type_hint_token'] += $targetPtr;
}
if ($param['type_hint_end_token'] !== false) {
$expected[$key]['type_hint_end_token'] += $targetPtr;
}
if ($param['comma_token'] !== false) {
$expected[$key]['comma_token'] += $targetPtr;
}
if (isset($param['default_token'])) {
$expected[$key]['default_token'] += $targetPtr;
}
if (isset($param['default_equal_token'])) {
$expected[$key]['default_equal_token'] += $targetPtr;
}
if (isset($param['visibility_token']) && $param['visibility_token'] !== false) {
$expected[$key]['visibility_token'] += $targetPtr;
}
if (isset($param['readonly_token'])) {
$expected[$key]['readonly_token'] += $targetPtr;
}
}

return $expected;
}

/**
* Verify that the build-in caching is used when caching is enabled.
* Test passing a non-existent token pointer.
*
* @return void
*/
public function testResultIsCached()
public function testNonExistentToken()
{
// The test case used is specifically selected to be one which will always reach the cache check.
$methodName = 'PHPCSUtils\\Utils\\FunctionDeclarations::getParameters';
$testMarker = '/* testPHP82PseudoTypeTrue */';
$expected = [
0 => [
'token' => 7, // Offset from the T_FUNCTION token.
'name' => '$var',
'content' => '?true $var = true',
'default' => 'true',
'default_token' => 11, // Offset from the T_FUNCTION token.
'default_equal_token' => 9, // Offset from the T_FUNCTION token.
'has_attributes' => false,
'pass_by_reference' => false,
'reference_token' => false,
'variable_length' => false,
'variadic_token' => false,
'type_hint' => '?true',
'type_hint_token' => 5, // Offset from the T_FUNCTION token.
'type_hint_end_token' => 5, // Offset from the T_FUNCTION token.
'nullable_type' => true,
'comma_token' => false,
],
];

$stackPtr = $this->getTargetToken($testMarker, Collections::functionDeclarationTokens());
$expected = $this->updateExpectedTokenPositions($stackPtr, $expected);

// Verify the caching works.
$origStatus = Cache::$enabled;
Cache::$enabled = true;

$resultFirstRun = FunctionDeclarations::getParameters(self::$phpcsFile, $stackPtr);
$isCached = Cache::isCached(self::$phpcsFile, $methodName, $stackPtr);
$resultSecondRun = FunctionDeclarations::getParameters(self::$phpcsFile, $stackPtr);

if ($origStatus === false) {
Cache::clear();
}
Cache::$enabled = $origStatus;
$this->expectPhpcsException('$stackPtr must be of type T_FUNCTION, T_CLOSURE or T_USE or an arrow function');

$this->assertSame($expected, $resultFirstRun, 'First result did not match expectation');
$this->assertTrue($isCached, 'Cache::isCached() could not find the cached value');
$this->assertSame($resultFirstRun, $resultSecondRun, 'Second result did not match first');
FunctionDeclarations::getParameters(self::$phpcsFile, 10000);
}
}

0 comments on commit d1ddb2c

Please sign in to comment.