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
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddParamTypeSplFixedArrayRector\Fixture;

use PhpCsFixer\Tokenizer\Tokens;

final class PlainDoc
{
/**
* @param Tokens $tokens
*/
public function someFunction(Tokens $tokens)
{
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddParamTypeSplFixedArrayRector\Fixture;

use PhpCsFixer\Tokenizer\Tokens;

final class PlainDoc
{
/**
* @param \PhpCsFixer\Tokenizer\Tokens<\PhpCsFixer\Tokenizer\Token> $tokens
*/
public function someFunction(Tokens $tokens)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace PhpCsFixer\Tokenizer;

final class SkipSameNamespaceGenericDoc
{
/**
* @param Tokens<Token> $tokens
*/
public function someFunction(Tokens $tokens)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddParamTypeSplFixedArrayRector\Fixture;

use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

final class SkipShortNamedGenericDoc
{
/**
* @param Tokens<Token> $tokens
*/
public function someFunction(Tokens $tokens)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@

use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger;
use Rector\Rector\AbstractRector;
use Rector\RuleDoc\CodeSample\CodeSample;
use Rector\RuleDoc\RuleDefinition;
use Rector\StaticTypeMapper\StaticTypeMapper;

/**
* @see \Rector\Tests\TypeDeclaration\Rector\FunctionLike\AddParamTypeSplFixedArrayRector\AddParamTypeSplFixedArrayRectorTest
Expand All @@ -34,6 +39,7 @@ final class AddParamTypeSplFixedArrayRector extends AbstractRector
public function __construct(
private readonly PhpDocTypeChanger $phpDocTypeChanger,
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly StaticTypeMapper $staticTypeMapper,
) {
}

Expand Down Expand Up @@ -118,6 +124,11 @@ public function refactor(Node $node): ?Node

$functionLikePhpDocInfo ??= $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
$paramName = $this->getName($param);

if ($this->isAlreadyDocumented($functionLikePhpDocInfo, $param, $paramName, $genericParamType)) {
continue;
}

$changedParamType = $this->phpDocTypeChanger->changeParamType(
$node,
$functionLikePhpDocInfo,
Expand All @@ -138,6 +149,37 @@ public function refactor(Node $node): ?Node
return null;
}

/**
* An existing "Tokens<Token>" docblock, with short or fully qualified names, resolves to "iterable<Token>&Tokens",
* as the fixed array class is not generic itself. That never equals the generic object type, so the tag would be
* rewritten with fully qualified names on every run.
*/
private function isAlreadyDocumented(
PhpDocInfo $phpDocInfo,
Param $param,
string $paramName,
GenericObjectType $genericObjectType
): bool {
$paramTagValueNode = $phpDocInfo->getParamTagValueByName($paramName);
if (! $paramTagValueNode instanceof ParamTagValueNode) {
return false;
}

$genericTypeNode = $paramTagValueNode->type;
if (! $genericTypeNode instanceof GenericTypeNode || count($genericTypeNode->genericTypes) !== 1) {
return false;
}

$currentType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType($genericTypeNode->type, $param);
$currentItemType = $this->staticTypeMapper->mapPHPStanPhpDocTypeNodeToPHPStanType(
$genericTypeNode->genericTypes[0],
$param
);

return $currentType->equals(new ObjectType($genericObjectType->getClassName()))
&& $currentItemType->equals($genericObjectType->getTypes()[0]);
}

private function resolveGenericType(TypeWithClassName $typeWithClassName): GenericObjectType|null
{
foreach (self::SPL_FIXED_ARRAY_TO_SINGLE as $fixedArrayClass => $singleClass) {
Expand Down
13 changes: 13 additions & 0 deletions stubs/PhpCsFixer/Tokenizer/Token.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace PhpCsFixer\Tokenizer;

if (class_exists('PhpCsFixer\Tokenizer\Token')) {
return;
}

final class Token
{
}
18 changes: 18 additions & 0 deletions stubs/PhpCsFixer/Tokenizer/Tokens.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace PhpCsFixer\Tokenizer;

use SplFixedArray;

if (class_exists('PhpCsFixer\Tokenizer\Tokens')) {
return;
}

/**
* @extends SplFixedArray<Token>
*/
class Tokens extends SplFixedArray
{
}
Loading