diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/keep_native_description.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/keep_native_description.php.inc index 00a5fe9ea24..00518c30fc7 100644 --- a/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/keep_native_description.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/keep_native_description.php.inc @@ -4,15 +4,6 @@ namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Property\MergePhpstanDocT final class KeepNativeDescription { - /** - * @param string|array|null $excludePattern Optional regex; classes matching this are excluded - * from the layer even when $pattern matches. - * @phpstan-param string|list|null $excludePattern - */ - public function layerPattern(string $name, string|array $pattern, string|array|null $excludePattern = null): self - { - } - /** * @return array some items * @phpstan-return list @@ -36,14 +27,6 @@ namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Property\MergePhpstanDocT final class KeepNativeDescription { - /** - * @param string|list|null $excludePattern Optional regex; classes matching this are excluded - * from the layer even when $pattern matches. - */ - public function layerPattern(string $name, string|array $pattern, string|array|null $excludePattern = null): self - { - } - /** * @return list some items */ diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/multiline_continue_next_tag.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/multiline_continue_next_tag.php.inc new file mode 100644 index 00000000000..9b05140de79 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/multiline_continue_next_tag.php.inc @@ -0,0 +1,49 @@ +|null $excludePattern + * @phpstan-param list $pattern + * @phpstan-param array{ + * strict: bool, + * } $options + * @return array + * @phpstan-return list + */ + public function run(array $pattern, ?array $excludePattern, array $options): array + { + } +} + +?> +----- +|null $excludePattern + * @param list $pattern + * @phpstan-param array{ + * strict: bool, + * } $options + * @return list + */ + public function run(array $pattern, ?array $excludePattern, array $options): array + { + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/skip_multiline_native_description.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/skip_multiline_native_description.php.inc new file mode 100644 index 00000000000..b1d9c354420 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/skip_multiline_native_description.php.inc @@ -0,0 +1,15 @@ +|null $excludePattern + */ + public function layerPattern(string $name, string|array $pattern, string|array|null $excludePattern = null): self + { + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/skip_multiline_phpstan_tag.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/skip_multiline_phpstan_tag.php.inc new file mode 100644 index 00000000000..b21f2b59ed0 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Property/MergePhpstanDocTagIntoNativeRector/Fixture/skip_multiline_phpstan_tag.php.inc @@ -0,0 +1,27 @@ +getPhpDocNode(); - $phpstanParamNames = []; - $phpstanVarNames = []; - $hasPhpstanReturn = false; + $phpstanTagKeys = []; + $multilineTagKeys = []; $nativeDescriptions = []; foreach ($phpDocNode->children as $phpDocChildNode) { @@ -110,64 +111,60 @@ public function refactor(Node $node): ?Node continue; } - // remember native tag descriptions, so they survive when the native tag is overruled - if (in_array($phpDocChildNode->name, self::PHPSTAN_TAG_TO_NATIVE_TAG, true)) { - $tagValueNode = $phpDocChildNode->value; - if (($tagValueNode instanceof ParamTagValueNode || $tagValueNode instanceof VarTagValueNode || $tagValueNode instanceof ReturnTagValueNode) && $tagValueNode->description !== '') { - $nativeDescriptions[$this->resolveTagKey($tagValueNode)] = $tagValueNode->description; - } - + $tagValueNode = $this->resolveMergeableTagValueNode($phpDocChildNode); + if (! $tagValueNode instanceof PhpDocTagValueNode) { continue; } - if ($phpDocChildNode->name === '@phpstan-param' && $phpDocChildNode->value instanceof ParamTagValueNode) { - $phpstanParamNames[] = $phpDocChildNode->value->parameterName; - } elseif ($phpDocChildNode->name === '@phpstan-var' && $phpDocChildNode->value instanceof VarTagValueNode) { - $phpstanVarNames[] = $phpDocChildNode->value->variableName; - } elseif ($phpDocChildNode->name === '@phpstan-return' && $phpDocChildNode->value instanceof ReturnTagValueNode) { - $hasPhpstanReturn = true; + $tagKey = $this->resolveTagKey($tagValueNode); + + // skip multiline tag, as phpcs does not support multiline native tag yet + if ($this->isMultiline($phpDocChildNode)) { + $multilineTagKeys[] = $tagKey; + } + + if (isset(self::PHPSTAN_TAG_TO_NATIVE_TAG[$phpDocChildNode->name])) { + $phpstanTagKeys[] = $tagKey; + } elseif ($tagValueNode->description !== '') { + // remember native tag descriptions, so they survive when the native tag is overruled + $nativeDescriptions[$tagKey] = $tagValueNode->description; } } - if ($phpstanParamNames === [] && $phpstanVarNames === [] && ! $hasPhpstanReturn) { + $mergeableTagKeys = array_diff($phpstanTagKeys, $multilineTagKeys); + if ($mergeableTagKeys === []) { return null; } - $hasChanged = false; - foreach ($phpDocNode->children as $key => $phpDocChildNode) { if (! $phpDocChildNode instanceof PhpDocTagNode) { continue; } + $tagValueNode = $this->resolveMergeableTagValueNode($phpDocChildNode); + if (! $tagValueNode instanceof PhpDocTagValueNode) { + continue; + } + + $tagKey = $this->resolveTagKey($tagValueNode); + if (! in_array($tagKey, $mergeableTagKeys, true)) { + continue; + } + + $nativeTagName = self::PHPSTAN_TAG_TO_NATIVE_TAG[$phpDocChildNode->name] ?? null; + // drop the weaker native tag that the @phpstan-* variant overrules - if ($this->isOverruledNativeTag( - $phpDocChildNode, - $phpstanParamNames, - $phpstanVarNames, - $hasPhpstanReturn - )) { + if ($nativeTagName === null) { unset($phpDocNode->children[$key]); - $hasChanged = true; continue; } - // promote the @phpstan-* tag to its native counterpart - $nativeTagName = self::PHPSTAN_TAG_TO_NATIVE_TAG[$phpDocChildNode->name] ?? null; - if ($nativeTagName !== null) { - // keep the description of the overruled native tag - $tagValueNode = $phpDocChildNode->value; - if (($tagValueNode instanceof ParamTagValueNode || $tagValueNode instanceof VarTagValueNode || $tagValueNode instanceof ReturnTagValueNode) && $tagValueNode->description === '') { - $tagValueNode->description = $nativeDescriptions[$this->resolveTagKey($tagValueNode)] ?? ''; - } - - $phpDocNode->children[$key] = new PhpDocTagNode($nativeTagName, $phpDocChildNode->value); - $hasChanged = true; + // promote the @phpstan-* tag to its native counterpart, keeping the description of the overruled native tag + if ($tagValueNode->description === '') { + $tagValueNode->description = $nativeDescriptions[$tagKey] ?? ''; } - } - if (! $hasChanged) { - return null; + $phpDocNode->children[$key] = new PhpDocTagNode($nativeTagName, $tagValueNode); } $phpDocNode->children = array_values($phpDocNode->children); @@ -188,29 +185,31 @@ public function refactor(Node $node): ?Node return $node; } - /** - * @param string[] $phpstanParamNames - * @param string[] $phpstanVarNames - */ - private function isOverruledNativeTag( - PhpDocTagNode $phpDocTagNode, - array $phpstanParamNames, - array $phpstanVarNames, - bool $hasPhpstanReturn - ): bool { - if ($phpDocTagNode->name === '@param' && $phpDocTagNode->value instanceof ParamTagValueNode) { - return in_array($phpDocTagNode->value->parameterName, $phpstanParamNames, true); + private function resolveMergeableTagValueNode( + PhpDocTagNode $phpDocTagNode + ): ParamTagValueNode|VarTagValueNode|ReturnTagValueNode|null { + if (! isset(self::PHPSTAN_TAG_TO_NATIVE_TAG[$phpDocTagNode->name]) && ! in_array( + $phpDocTagNode->name, + self::PHPSTAN_TAG_TO_NATIVE_TAG, + true + )) { + return null; } - if ($phpDocTagNode->name === '@var' && $phpDocTagNode->value instanceof VarTagValueNode) { - return in_array($phpDocTagNode->value->variableName, $phpstanVarNames, true); + $tagValueNode = $phpDocTagNode->value; + if ($tagValueNode instanceof ParamTagValueNode || $tagValueNode instanceof VarTagValueNode || $tagValueNode instanceof ReturnTagValueNode) { + return $tagValueNode; } - if ($phpDocTagNode->name === '@return' && $phpDocTagNode->value instanceof ReturnTagValueNode) { - return $hasPhpstanReturn; - } + return null; + } + + private function isMultiline(PhpDocTagNode $phpDocTagNode): bool + { + $startLine = $phpDocTagNode->getAttribute(Attribute::START_LINE); + $endLine = $phpDocTagNode->getAttribute(Attribute::END_LINE); - return false; + return is_int($startLine) && is_int($endLine) && $endLine > $startLine; } private function resolveTagKey(ParamTagValueNode|VarTagValueNode|ReturnTagValueNode $tagValueNode): string