Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArgumentsAnalyzer - support PHP8.1 readonly #6224

Merged
merged 1 commit into from Jan 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -98,8 +98,8 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$functionsAnalyzer = new FunctionsAnalyzer();

$tokenKinds = [T_FUNCTION];

if (\PHP_VERSION_ID >= 70400) {
$tokenKinds[] = T_FN;
}
Expand All @@ -112,7 +112,6 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
}

$arguments = $functionsAnalyzer->getFunctionArguments($tokens, $index);

$this->fixFunctionParameters($tokens, $arguments);
}
}
Expand Down
24 changes: 17 additions & 7 deletions src/Tokenizer/Analyzer/ArgumentsAnalyzer.php
Expand Up @@ -87,6 +87,16 @@ public function getArguments(Tokens $tokens, int $openParenthesis, int $closePar

public function getArgumentInfo(Tokens $tokens, int $argumentStart, int $argumentEnd): ArgumentAnalysis
{
static $skipTypes = null;

if (null === $skipTypes) {
$skipTypes = [T_ELLIPSIS, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE];

if (\defined('T_READONLY')) { // @TODO: drop condition when PHP 8.1+ is required
SpacePossum marked this conversation as resolved.
Show resolved Hide resolved
$skipTypes[] = T_READONLY;
}
}

$info = [
'default' => null,
'name' => null,
Expand All @@ -101,10 +111,16 @@ public function getArgumentInfo(Tokens $tokens, int $argumentStart, int $argumen
for ($index = $argumentStart; $index <= $argumentEnd; ++$index) {
$token = $tokens[$index];

if (\defined('T_ATTRIBUTE') && $token->isGivenKind(T_ATTRIBUTE)) {
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);

continue;
}

if (
$token->isComment()
|| $token->isWhitespace()
|| $token->isGivenKind([T_ELLIPSIS, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE])
|| $token->isGivenKind($skipTypes)
|| $token->equals('&')
) {
continue;
Expand All @@ -122,12 +138,6 @@ public function getArgumentInfo(Tokens $tokens, int $argumentStart, int $argumen
continue;
}

if (\defined('T_ATTRIBUTE') && $token->isGivenKind(T_ATTRIBUTE)) {
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $index);

continue;
}

if ($sawName) {
$info['default'] .= $token->getContent();
} else {
Expand Down
Expand Up @@ -500,4 +500,27 @@ function foo2(?string &/*comment*/$param2 = null) {}
yield [$cases[0], $cases[1]];
yield [$cases[1], $cases[0], ['use_nullable_type_declaration' => false]];
}

/**
* @dataProvider provideFix81Cases
* @requires PHP 8.1
*/
public function testFix81(string $expected): void
{
$this->doTest($expected);
}

public function provideFix81Cases(): iterable
{
yield [
'<?php
class Foo
{
public function __construct(
protected readonly ?bool $nullable = null,
) {}
}
',
];
}
}
88 changes: 71 additions & 17 deletions tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php
Expand Up @@ -86,16 +86,14 @@ public function testArguments73(string $code, int $openIndex, int $closeIndex, a
static::assertSame($arguments, $analyzer->getArguments($tokens, $openIndex, $closeIndex));
}

public function provideArguments73Cases(): array
public function provideArguments73Cases(): iterable
{
return [
['<?php foo($a,);', 2, 5, [3 => 3]],
['<?php foo($a,/**/);', 2, 6, [3 => 3]],
['<?php foo($a(1,2,3,4,5),);', 2, 16, [3 => 14]],
['<?php foo($a(1,2,3,4,5,),);', 2, 17, [3 => 15]],
['<?php foo($a(1,2,3,4,5,),);', 4, 15, [5 => 5, 7 => 7, 9 => 9, 11 => 11, 13 => 13]],
['<?php bar($a, $b , ) ;', 2, 10, [3 => 3, 5 => 7]],
];
yield ['<?php foo($a,);', 2, 5, [3 => 3]];
yield ['<?php foo($a,/**/);', 2, 6, [3 => 3]];
yield ['<?php foo($a(1,2,3,4,5),);', 2, 16, [3 => 14]];
yield ['<?php foo($a(1,2,3,4,5,),);', 2, 17, [3 => 15]];
yield ['<?php foo($a(1,2,3,4,5,),);', 4, 15, [5 => 5, 7 => 7, 9 => 9, 11 => 11, 13 => 13]];
yield ['<?php bar($a, $b , ) ;', 2, 10, [3 => 3, 5 => 7]];
}

/**
Expand Down Expand Up @@ -146,10 +144,7 @@ public function testArgumentInfo(string $code, int $openIndex, int $closeIndex,
$tokens = Tokens::fromCode($code);
$analyzer = new ArgumentsAnalyzer();

static::assertSame(
serialize($expected),
serialize($analyzer->getArgumentInfo($tokens, $openIndex, $closeIndex))
);
self::assertArgumentAnalysis($expected, $analyzer->getArgumentInfo($tokens, $openIndex, $closeIndex));
}

public function provideArgumentsInfoCases(): \Generator
Expand Down Expand Up @@ -284,10 +279,7 @@ public function testArgumentInfo80(string $code, int $openIndex, int $closeIndex
$tokens = Tokens::fromCode($code);
$analyzer = new ArgumentsAnalyzer();

static::assertSame(
serialize($expected),
serialize($analyzer->getArgumentInfo($tokens, $openIndex, $closeIndex))
);
self::assertArgumentAnalysis($expected, $analyzer->getArgumentInfo($tokens, $openIndex, $closeIndex));
}

public function provideArgumentsInfo80Cases(): \Generator
Expand Down Expand Up @@ -326,4 +318,66 @@ public function provideArgumentsInfo80Cases(): \Generator
];
}
}

/**
* @requires PHP 8.1
* @dataProvider provideArgumentsInfo81Cases
*/
public function testArgumentInfo81(string $code, int $openIndex, int $closeIndex, ArgumentAnalysis $expected): void
{
$tokens = Tokens::fromCode($code);
$analyzer = new ArgumentsAnalyzer();

self::assertArgumentAnalysis($expected, $analyzer->getArgumentInfo($tokens, $openIndex, $closeIndex));
}

public function provideArgumentsInfo81Cases(): \Generator
{
yield [
'<?php
class Foo
{
public function __construct(
protected readonly ?bool $nullable = true,
) {}
}
',
13,
25,
new ArgumentAnalysis(
'$nullable',
21,
'true',
new TypeAnalysis(
'?bool',
18,
19
)
),
];
}

private static function assertArgumentAnalysis(ArgumentAnalysis $expected, ArgumentAnalysis $actual): void
{
static::assertSame($expected->getDefault(), $actual->getDefault(), 'Default.');
static::assertSame($expected->getName(), $actual->getName(), 'Name.');
static::assertSame($expected->getNameIndex(), $actual->getNameIndex(), 'Name index.');
static::assertSame($expected->hasDefault(), $actual->hasDefault(), 'Has default.');
static::assertSame($expected->hasTypeAnalysis(), $actual->hasTypeAnalysis(), 'Has type analysis.');

if ($expected->hasTypeAnalysis()) {
$expectedTypeAnalysis = $expected->getTypeAnalysis();
$actualTypeAnalysis = $actual->getTypeAnalysis();

static::assertSame($expectedTypeAnalysis->getEndIndex(), $actualTypeAnalysis->getEndIndex(), 'Type analysis end index.');
static::assertSame($expectedTypeAnalysis->getName(), $actualTypeAnalysis->getName(), 'Type analysis name.');
static::assertSame($expectedTypeAnalysis->getStartIndex(), $actualTypeAnalysis->getStartIndex(), 'Type analysis start index.');
static::assertSame($expectedTypeAnalysis->isNullable(), $actualTypeAnalysis->isNullable(), 'Type analysis nullable.');
static::assertSame($expectedTypeAnalysis->isReservedType(), $actualTypeAnalysis->isReservedType(), 'Type analysis reserved type.');
} else {
static::assertNull($actual->getTypeAnalysis());
}

static::assertSame(serialize($expected), serialize($actual));
}
}