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
1 change: 1 addition & 0 deletions config/level/symfony/symfony-code-quality.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
services:
Rector\Symfony\Rector\BinaryOp\ResponseStatusCodeRector: ~
Rector\Symfony\Rector\Class_\MakeCommandLazyRector: ~
2 changes: 2 additions & 0 deletions ecs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ parameters:

Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
# tough logic
- 'packages/BetterPhpDocParser/src/'
- 'packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php'
- 'packages/Legacy/src/Rector/ClassMethod/ChangeSingletonToServiceRector.php'
- 'src/Rector/Psr4/MultipleClassFileToPsr4ClassesRector.php'
- 'src/PhpParser/Node/Resolver/NameResolver.php'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

final class Attribute
{
/**
* @var string
*/
public const HAS_DESCRIPTION_WITH_ORIGINAL_SPACES = 'has_description_with_restored_spaces';

/**
* Fully-qualified name
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPStan\PhpDocParser\Ast\Node;

use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
Expand Down Expand Up @@ -108,14 +109,23 @@ private function parseChildAndStoreItsPositions(TokenIterator $tokenIterator): N
$attributeAwareNode = $this->attributeAwareNodeFactory->createFromNode($node);
$attributeAwareNode->setAttribute(Attribute::PHP_DOC_NODE_INFO, new StartEndInfo($tokenStart, $tokenEnd));

// add original text, for keeping trimmed spaces
$possibleMultilineText = null;
if ($attributeAwareNode instanceof PhpDocTagNode) {
if (property_exists($attributeAwareNode->value, 'description')) {
$possibleMultilineText = $attributeAwareNode->value->description;
}
}

if ($attributeAwareNode instanceof PhpDocTextNode) {
$originalContent = $this->getOriginalContentFromTokenIterator($tokenIterator);
$possibleMultilineText = $attributeAwareNode->text;
}

$currentText = $attributeAwareNode->text;
if ($possibleMultilineText) {
// add original text, for keeping trimmed spaces
$originalContent = $this->getOriginalContentFromTokenIterator($tokenIterator);

// we try to match original content without trimmed spaces
$currentTextPattern = '#' . preg_quote($currentText, '#') . '#s';
$currentTextPattern = '#' . preg_quote($possibleMultilineText, '#') . '#s';
$currentTextPattern = Strings::replace($currentTextPattern, '#\s#', '\s+');
$match = Strings::match($originalContent, $currentTextPattern);

Expand Down
71 changes: 61 additions & 10 deletions packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ private function printNode(

/** @var StartEndInfo|null $tokenStartEndInfo */
$startEndInfo = $attributeAwareNode->getAttribute(Attribute::PHP_DOC_NODE_INFO) ?: $startEndInfo;
$attributeAwareNode = $this->fixMultilineDescriptions($attributeAwareNode);

if ($startEndInfo) {
$isLastToken = ($nodeCount === $i);
Expand Down Expand Up @@ -158,11 +159,6 @@ private function printNode(
);
}

// fix multiline BC break - https://github.com/phpstan/phpdoc-parser/pull/26/files
if ($attributeAwareNode->getAttribute(Attribute::ORIGINAL_CONTENT)) {
$this->restoreOriginalSpacingInText($attributeAwareNode);
}

$content = (string) $attributeAwareNode;
$content = explode(PHP_EOL, $content);
$content = implode(PHP_EOL . ' * ', $content);
Expand Down Expand Up @@ -209,6 +205,9 @@ private function addTokensFromTo(
return $this->appendToOutput($output, $from, $to, $positionJumpSet);
}

/**
* @param PhpDocTagNode|AttributeAwareNodeInterface $phpDocTagNode
*/
private function printPhpDocTagNode(
PhpDocTagNode $phpDocTagNode,
StartEndInfo $startEndInfo,
Expand All @@ -222,6 +221,17 @@ private function printPhpDocTagNode(
$output .= ' ';
}

if ($phpDocTagNode->getAttribute(Attribute::HAS_DESCRIPTION_WITH_ORIGINAL_SPACES)) {
if (property_exists($phpDocTagNode->value, 'description') && $phpDocTagNode->value->description) {
$pattern = Strings::replace($phpDocTagNode->value->description, '#[\s]+#', '\s+');
$nodeOutput = Strings::replace($nodeOutput, '#' . $pattern . '#', $phpDocTagNode->value->description);

if (substr_count($nodeOutput, "\n")) {
$nodeOutput = Strings::replace($nodeOutput, "#\n#", PHP_EOL . ' * ');
}
}
}

return $output . $nodeOutput;
}

Expand Down Expand Up @@ -290,17 +300,29 @@ private function isTagSeparatedBySpace(string $nodeOutput, PhpDocTagNode $phpDoc
/**
* @param PhpDocTextNode|AttributeAwareNodeInterface $attributeAwareNode
*/
private function restoreOriginalSpacingInText(AttributeAwareNodeInterface $attributeAwareNode): void
{
private function restoreOriginalSpacingInText(
AttributeAwareNodeInterface $attributeAwareNode
): ?AttributeAwareNodeInterface {
/** @var string $originalContent */
$originalContent = $attributeAwareNode->getAttribute(Attribute::ORIGINAL_CONTENT);
$oldSpaces = Strings::matchAll($originalContent, '#\s+#ms');

$newParts = Strings::split($attributeAwareNode->text, '#\s+#');
$currentText = null;
if ($attributeAwareNode instanceof PhpDocTagNode) {
if (property_exists($attributeAwareNode->value, 'description')) {
$currentText = $attributeAwareNode->value->description;
}
}

if ($attributeAwareNode instanceof PhpDocTextNode) {
$currentText = $attributeAwareNode->text;
}

$newParts = Strings::split($currentText, '#\s+#');

// we can't do this!
if (count($oldSpaces) + 1 !== count($newParts)) {
return;
return null;
}

$newText = '';
Expand All @@ -317,7 +339,36 @@ private function restoreOriginalSpacingInText(AttributeAwareNodeInterface $attri
}

if ($newText) {
$attributeAwareNode->text = $newText;
if ($attributeAwareNode instanceof PhpDocTagNode) {
if (property_exists($attributeAwareNode->value, 'description')) {
$attributeAwareNode->value->description = $newText;
return $attributeAwareNode;
}
}

if ($attributeAwareNode instanceof PhpDocTextNode) {
$attributeAwareNode->text = $newText;
return $attributeAwareNode;
}
}

return null;
}

/**
* Fix multiline BC break - https://github.com/phpstan/phpdoc-parser/pull/26/files
*/
private function fixMultilineDescriptions(AttributeAwareNodeInterface $attributeAwareNode): AttributeAwareNodeInterface
{
if (! $attributeAwareNode->getAttribute(Attribute::ORIGINAL_CONTENT)) {
return $attributeAwareNode;
}

$nodeWithRestoredSpaces = $this->restoreOriginalSpacingInText($attributeAwareNode);
if ($nodeWithRestoredSpaces !== null) {
$attributeAwareNode = $nodeWithRestoredSpaces;
$attributeAwareNode->setAttribute(Attribute::HAS_DESCRIPTION_WITH_ORIGINAL_SPACES, true);
}
return $attributeAwareNode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @param string|null $channelId Also if we do the check in the self::execute method,
* allow for null to make PHPStan pass
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @return string|null Also if we do the check in the self::execute method,
* allow for null to make PHPStan pass
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @deprecated Also if we do the check in the self::execute method,
* allow for null to make PHPStan pass
*/
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public function provideMultiline(): Iterator
{
yield [__DIR__ . '/PhpDocInfoPrinterSource/multiline1.txt'];
yield [__DIR__ . '/PhpDocInfoPrinterSource/multiline2.txt'];
yield [__DIR__ . '/PhpDocInfoPrinterSource/multiline3.txt'];
yield [__DIR__ . '/PhpDocInfoPrinterSource/multiline4.txt'];
yield [__DIR__ . '/PhpDocInfoPrinterSource/multiline5.txt'];
}

/**
Expand Down
155 changes: 155 additions & 0 deletions packages/Symfony/src/Rector/Class_/MakeCommandLazyRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?php declare(strict_types=1);

namespace Rector\Symfony\Rector\Class_;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;

/**
* @see https://symfony.com/doc/current/console/commands_as_services.html
*/
final class MakeCommandLazyRector extends AbstractRector
{
/**
* @var string
*/
private const COMMAND_CLASS = 'Symfony\Component\Console\Command\Command';

/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;

public function __construct(CallableNodeTraverser $callableNodeTraverser)
{
$this->callableNodeTraverser = $callableNodeTraverser;
}

public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Make Symfony commands lazy', [
new CodeSample(
<<<'CODE_SAMPLE'
use Symfony\Component\Console\Command\Command

class SunshineCommand extends Command
{
public function configure()
{
$this->setName('sunshine');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Symfony\Component\Console\Command\Command

class SunshineCommand extends Command
{
protected static $defaultName = 'sunshine';
public function configure()
{
}
}
CODE_SAMPLE
),
]);
}

/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}

/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isType($node, self::COMMAND_CLASS)) {
return null;
}

$commandName = $this->resolveCommandNameAndRemove($node);
if ($commandName === null) {
return null;
}

$defaultNameProperty = $this->createDefaultNameProperty($commandName);
$node->stmts = array_merge([$defaultNameProperty], (array) $node->stmts);

return $node;
}

private function createDefaultNameProperty(Node $commandNameNode): Node\Stmt\Property
{
return $this->builderFactory->property('defaultName')
->makeProtected()
->makeStatic()
->setDefault($commandNameNode)
->getNode();
}

private function resolveCommandNameAndRemove(Class_ $class): ?Node
{
$commandName = null;
$this->callableNodeTraverser->traverseNodesWithCallable((array) $class->stmts, function (Node $node) use (
&$commandName
) {
if ($node instanceof MethodCall) {
if (! $this->isName($node, 'setName')) {
return null;
}

$commandName = $node->args[0]->value;
$this->removeNode($node);
}

if ($node instanceof ClassMethod && $this->isName($node, '__construct')) {
if (count((array) $node->stmts) !== 1) {
return null;
}

$onlyNode = $node->stmts[0];
if ($onlyNode instanceof Expression) {
$onlyNode = $onlyNode->expr;
if (! $this->isName($onlyNode, '__construct')) {
return null;
}

$commandName = $onlyNode->args[0]->value;
if (! is_string($this->getValue($commandName))) {
return null;
}

if (count($node->params) === 0) {
$this->removeNode($node);
}
}
}

if ($node instanceof StaticCall) {
if (! $this->isName($node, '__construct')) {
return null;
}

$commandName = $node->args[0]->value;

array_shift($node->args);
}
});

return $commandName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;

class ConstantDefinedName extends \Symfony\Component\Console\Command\Command
{
private const COMMAND_NAME = 'regge';

public function configure()
{
$this->setName(self::COMMAND_NAME);
}
}

?>
-----
<?php

namespace Rector\Symfony\Tests\Rector\Class_\MakeCommandLazyRector\Fixture;

class ConstantDefinedName extends \Symfony\Component\Console\Command\Command
{
protected static $defaultName = self::COMMAND_NAME;
private const COMMAND_NAME = 'regge';

public function configure()
{
}
}

?>
Loading