diff --git a/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/ConvertMultilineDocblockToSingleLineRectorTest.php b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/ConvertMultilineDocblockToSingleLineRectorTest.php new file mode 100644 index 00000000000..0b03cd0ca4f --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/ConvertMultilineDocblockToSingleLineRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/single_statements.php.inc b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/single_statements.php.inc new file mode 100644 index 00000000000..6ec3ed18c0d --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/single_statements.php.inc @@ -0,0 +1,137 @@ +value; + } + + /** + * @param string $value + */ + public function setValue(string $value): void + { + $this->value = $value; + } + + /** + * @throws \Exception + */ + public function dangerous(): void + { + throw new \Exception(); + } + + public function testFunction() + { + /** + * @var Foo $foo + */ + $foo = resolve(Foo::class); + + /** + * @phpstan-ignore variable.undefined + */ + echo $bar; + + /** + * @var string + */ + $name = 'test'; + + /** + * @param int $i + */ + if ($i > 0) { + /** + * @var array $data + */ + $data = []; + } + + /** + * @var int $number + */ + foreach ($items as $number) { + // do something + } + + /** + * @return string + */ + return $name; + } +} + +?> +----- +value; + } + + /** @param string $value */ + public function setValue(string $value): void + { + $this->value = $value; + } + + /** @throws \Exception */ + public function dangerous(): void + { + throw new \Exception(); + } + + public function testFunction() + { + /** @var Foo $foo */ + $foo = resolve(Foo::class); + + /** @phpstan-ignore variable.undefined */ + echo $bar; + + /** @var string */ + $name = 'test'; + + /** @param int $i */ + if ($i > 0) { + /** @var array $data */ + $data = []; + } + + /** @var int $number */ + foreach ($items as $number) { + // do something + } + + /** @return string */ + return $name; + } +} + +?> diff --git a/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/skip_multiple_line_statements.php.inc b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/skip_multiple_line_statements.php.inc new file mode 100644 index 00000000000..d6324c99d65 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/skip_multiple_line_statements.php.inc @@ -0,0 +1,43 @@ +title; + } + + /** + * @param string $value + * @return void + */ + public function setValue(string $value): void + { + $this->value = $value; + } + + /** + * @var string + * @see SomeClass + */ + private $name; + + /** + * This method does something dangerous. + * + * Please be careful when using it. + * + * @throws \Exception + */ + public function dangerous(): void + { + throw new \Exception(); + } +} diff --git a/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/skip_single_line_statements.php.inc b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/skip_single_line_statements.php.inc new file mode 100644 index 00000000000..f62882d4991 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/Fixture/skip_single_line_statements.php.inc @@ -0,0 +1,21 @@ +title; + } + + /** @param string $value */ + public function setValue(string $value): void + { + $this->value = $value; + } + + /** @var string */ + private $name; +} diff --git a/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/config/configured_rule.php b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/config/configured_rule.php new file mode 100644 index 00000000000..03a11476bc1 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([ConvertMultilineDocblockToSingleLineRector::class]); diff --git a/rules/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector.php b/rules/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector.php new file mode 100644 index 00000000000..9d280a27f11 --- /dev/null +++ b/rules/CodingStyle/Rector/FunctionLike/ConvertMultilineDocblockToSingleLineRector.php @@ -0,0 +1,160 @@ +> + */ + public function getNodeTypes(): array + { + return [ + Class_::class, + ClassMethod::class, + Function_::class, + Property::class, + Expression::class, + Echo_::class, + Return_::class, + If_::class, + Foreach_::class, + ]; + } + + public function refactor(Node $node): ?Node + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + + if (! $this->shouldConvertToSingleLine($phpDocInfo)) { + return null; + } + + $originalDocText = $docComment->getText(); + $inlinedDocText = $this->convertToSingleLine($originalDocText); + + if ($originalDocText === $inlinedDocText) { + return null; + } + + $node->setDocComment(new Doc($inlinedDocText)); + + return $node; + } + + private function convertToSingleLine(string $docComment): string + { + $content = trim($docComment); + $content = substr($content, 3); // Remove "/**" + $content = substr($content, 0, -2); // Remove "*/" + + $lines = preg_split('/\r\n|\r|\n/', $content) ?: []; + $cleanedLines = []; + + foreach ($lines as $line) { + $line = trim($line); + $line = preg_replace('/^\*\s?/', '', $line) ?: ''; + $line = trim($line); + + if ($line !== '') { + $cleanedLines[] = $line; + } + } + + $inlineContent = implode(' ', $cleanedLines); + + return '/** ' . $inlineContent . ' */'; + } + + private function shouldConvertToSingleLine(PhpDocInfo $phpDocInfo): bool + { + $phpDocNode = $phpDocInfo->getPhpDocNode(); + $children = $phpDocNode->children; + + $tagCount = 0; + $hasTextContent = false; + + foreach ($children as $child) { + if ($child instanceof PhpDocTagNode) { + ++$tagCount; + } elseif ($child instanceof PhpDocTextNode) { + $text = trim($child->text); + if ($text !== '' && $text !== '*') { + $hasTextContent = true; + } + } + } + + return $tagCount === 1 && ! $hasTextContent; + } +}