Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net)
[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE)
![Tests](https://img.shields.io/badge/tests-3799-brightgreen.svg)
![Tests](https://img.shields.io/badge/tests-3800-brightgreen.svg)
[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)

[![CI status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml/badge.svg)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml)
Expand Down
59 changes: 41 additions & 18 deletions src/Fixer/PromotedConstructorPropertyFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,16 @@ private function promoteProperties(Tokens $tokens, int $classIndex, ConstructorA
continue;
}

$tokensToInsert = self::removePropertyAndReturnTokensToInsert($tokens, $propertyIndex);
[$tokensToInsertBefore, $tokensToInsertAfter] = self::removePropertyAndReturnTokensToInsert($tokens, $propertyIndex);

self::renameVariable($tokens, $constructorAnalysis->getConstructorIndex(), $oldParameterName, $newParameterName);

self::removeAssignment($tokens, $constructorPromotableAssignments[$constructorParameterName]);
$this->updateParameterSignature(
$tokens,
$constructorParameterIndex,
$tokensToInsert,
$tokensToInsertBefore,
$tokensToInsertAfter,
\substr($propertyType, 0, 1) === '?',
);
}
Expand Down Expand Up @@ -316,12 +317,12 @@ private static function getClassProperties(Tokens $tokens, int $classIndex): arr
}

/**
* @return list<Token>
* @return array{list<Token>, list<Token>}
*/
private static function removePropertyAndReturnTokensToInsert(Tokens $tokens, ?int $propertyIndex): array
{
if ($propertyIndex === null) {
return [new Token([\T_PUBLIC, 'public'])];
return [[new Token([\T_PUBLIC, 'public'])], []];
}

$visibilityIndex = $tokens->getPrevTokenOfKind($propertyIndex, [[\T_PRIVATE], [\T_PROTECTED], [\T_PUBLIC], [\T_VAR]]);
Expand All @@ -342,21 +343,28 @@ private static function removePropertyAndReturnTokensToInsert(Tokens $tokens, ?i
$removeFrom++;
}

$tokensToInsert = [];
$tokensToInsertBefore = [];
for ($index = $removeFrom; $index <= $visibilityIndex - 1; $index++) {
$tokensToInsert[] = $tokens[$index];
$tokensToInsertBefore[] = $tokens[$index];
}

$visibilityToken = $tokens[$visibilityIndex];
if ($tokens[$visibilityIndex]->isGivenKind(\T_VAR)) {
$visibilityToken = new Token([\T_PUBLIC, 'public']);
}
$tokensToInsert[] = $visibilityToken;
$tokensToInsertBefore[] = $visibilityToken;

$tokensToInsertAfter = [];
if ($tokens[$removeTo]->isGivenKind(CT::T_PROPERTY_HOOK_BRACE_CLOSE)) {
for ($index = $propertyIndex + 1; $index <= $removeTo; $index++) {
$tokensToInsertAfter[] = $tokens[$index];
}
}

$tokens->clearRange($removeFrom + 1, $removeTo);
TokenRemover::removeWithLinesIfPossible($tokens, $removeFrom);

return $tokensToInsert;
return [$tokensToInsertBefore, $tokensToInsertAfter];
}

/**
Expand All @@ -377,6 +385,10 @@ private static function getTokenOfKindSibling(Tokens $tokens, int $direction, in
}
}

if ($tokens[$index]->isGivenKind(CT::T_PROPERTY_HOOK_BRACE_CLOSE)) {
break;
}

$index += $direction;
}

Expand Down Expand Up @@ -412,31 +424,42 @@ private static function removeAssignment(Tokens $tokens, int $variableAssignment
}

/**
* @param list<Token> $tokensToInsert
* @param list<Token> $tokensToInsertBefore
* @param list<Token> $tokensToInsertAfter
*/
private function updateParameterSignature(Tokens $tokens, int $constructorParameterIndex, array $tokensToInsert, bool $makeTypeNullable): void
{
private function updateParameterSignature(
Tokens $tokens,
int $constructorParameterIndex,
array $tokensToInsertBefore,
array $tokensToInsertAfter,
bool $makeTypeNullable
): void {
$prevElementIndex = $tokens->getPrevTokenOfKind($constructorParameterIndex, ['(', ',', [CT::T_ATTRIBUTE_CLOSE]]);
\assert(\is_int($prevElementIndex));

$propertyStartIndex = $tokens->getNextMeaningfulToken($prevElementIndex);
\assert(\is_int($propertyStartIndex));

foreach ($tokensToInsert as $index => $token) {
foreach ($tokensToInsertBefore as $index => $token) {
if ($token->isGivenKind(\T_PUBLIC)) {
$tokensToInsert[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $token->getContent()]);
$tokensToInsertBefore[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC, $token->getContent()]);
} elseif ($token->isGivenKind(\T_PROTECTED)) {
$tokensToInsert[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $token->getContent()]);
$tokensToInsertBefore[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED, $token->getContent()]);
} elseif ($token->isGivenKind(\T_PRIVATE)) {
$tokensToInsert[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $token->getContent()]);
$tokensToInsertBefore[$index] = new Token([CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE, $token->getContent()]);
}
}
$tokensToInsert[] = new Token([\T_WHITESPACE, ' ']);
$tokensToInsertBefore[] = new Token([\T_WHITESPACE, ' ']);

if ($makeTypeNullable && !$tokens[$propertyStartIndex]->isGivenKind(CT::T_NULLABLE_TYPE)) {
$tokensToInsert[] = new Token([CT::T_NULLABLE_TYPE, '?']);
$tokensToInsertBefore[] = new Token([CT::T_NULLABLE_TYPE, '?']);
}

$this->tokensToInsert[$propertyStartIndex] = $tokensToInsert;
$this->tokensToInsert[$propertyStartIndex] = $tokensToInsertBefore;

$nextPropertyStartIndex = $tokens->getNextMeaningfulToken($propertyStartIndex);
\assert(\is_int($nextPropertyStartIndex));

$this->tokensToInsert[$nextPropertyStartIndex + 1] = $tokensToInsertAfter;
}
}
37 changes: 37 additions & 0 deletions tests/Fixer/PromotedConstructorPropertyFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1067,4 +1067,41 @@ public function __construct(
];
}
}

/**
* @dataProvider provideFix84Cases
*
* @requires PHP >= 8.4
*/
public function testFix84(string $expected, ?string $input = null): void
{
$this->doTest($expected, $input);
}

/**
* @return iterable<array{0: string, 1?: string}>
*/
public static function provideFix84Cases(): iterable
{
yield 'promote property with hook' => [
<<<'PHP'
<?php class Foo {
public function __construct(
private string $bar { set (string $bar) { $this->bar = strtoupper($bar); } }
) {
}
}
PHP,
<<<'PHP'
<?php class Foo {
private string $bar { set (string $bar) { $this->bar = strtoupper($bar); } }
public function __construct(
string $bar
) {
$this->bar = $bar;
}
}
PHP,
];
}
}