Skip to content

Commit

Permalink
fix insert before
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Nov 8, 2024
1 parent 2d862a7 commit dbd1baf
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 49 deletions.
4 changes: 4 additions & 0 deletions src/NodeManipulator/ClassDependencyManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
use Rector\ValueObject\MethodName;
use Rector\ValueObject\PhpVersionFeature;

/**
* @see \Rector\Tests\NodeManipulator\ClassDependencyManipulatorTest
*/
final readonly class ClassDependencyManipulator
{
public function __construct(
Expand Down Expand Up @@ -143,6 +146,7 @@ private function addPromotedProperty(Class_ $class, PropertyMetadata $propertyMe
} else {
$constructClassMethod = $this->nodeFactory->createPublicMethod(MethodName::CONSTRUCT);
$constructClassMethod->params[] = $param;

$this->classInsertManipulator->addAsFirstMethod($class, $constructClassMethod);
}
}
Expand Down
88 changes: 39 additions & 49 deletions src/NodeManipulator/ClassInsertManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Rector\NodeManipulator;

use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
Expand All @@ -23,20 +22,53 @@ public function __construct(
/**
* @api
*/
public function addAsFirstMethod(Class_ $class, Property | ClassConst | ClassMethod $stmt): void
public function addAsFirstMethod(Class_ $class, Property | ClassConst | ClassMethod $addedStmt): void
{
$scope = $class->getAttribute(AttributeKey::SCOPE);
$stmt->setAttribute(AttributeKey::SCOPE, $scope);
$addedStmt->setAttribute(AttributeKey::SCOPE, $scope);

if ($this->isSuccessToInsertBeforeFirstMethod($class, $stmt)) {
// no stmts? add this one
if ($class->stmts === []) {
$class->stmts[] = $addedStmt;
return;
}

if ($this->isSuccessToInsertAfterLastProperty($class, $stmt)) {
return;
$newClassStmts = [];
$isAdded = false;

foreach ($class->stmts as $key => $classStmt) {
$nextStmt = $class->stmts[$key + 1] ?? null;

if ($isAdded === false) {
// first class method
if ($classStmt instanceof ClassMethod) {
$newClassStmts[] = $addedStmt;
$newClassStmts[] = $classStmt;
$isAdded = true;
continue;
}

// after last property
if ($classStmt instanceof Property && ! $nextStmt instanceof Property) {
$newClassStmts[] = $classStmt;
$newClassStmts[] = $addedStmt;
$isAdded = true;
continue;
}
}

$newClassStmts[] = $classStmt;
}

$class->stmts[] = $stmt;
// still not added? try after last trait
// @todo

if ($isAdded === false) {
// keep added at least as first stmt
$class->stmts = array_merge([$addedStmt], $class->stmts);
} else {
$class->stmts = $newClassStmts;
}
}

/**
Expand All @@ -52,46 +84,4 @@ public function addPropertyToClass(Class_ $class, string $name, ?Type $type): vo
$property = $this->nodeFactory->createPrivatePropertyFromNameAndType($name, $type);
$this->addAsFirstMethod($class, $property);
}

/**
* @param Stmt[] $stmts
* @return Stmt[]
*/
private function insertBefore(array $stmts, Stmt $stmt, int $key): array
{
array_splice($stmts, $key, 0, [$stmt]);

return $stmts;
}

private function isSuccessToInsertBeforeFirstMethod(Class_ $class, ClassConst|ClassMethod|Property $stmt): bool
{
foreach ($class->stmts as $key => $classStmt) {
if (! $classStmt instanceof ClassMethod) {
continue;
}

$class->stmts = $this->insertBefore($class->stmts, $stmt, $key);

return true;
}

return false;
}

private function isSuccessToInsertAfterLastProperty(Class_ $class, ClassConst|ClassMethod|Property $stmt): bool
{
$previousElement = null;
foreach ($class->stmts as $key => $classStmt) {
if ($previousElement instanceof Property && ! $classStmt instanceof Property) {
$class->stmts = $this->insertBefore($class->stmts, $stmt, $key);

return true;
}

$previousElement = $classStmt;
}

return false;
}
}
74 changes: 74 additions & 0 deletions tests/NodeManipulator/ClassDependencyManipulatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\NodeManipulator;

use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\PrettyPrinter\Standard;
use PHPStan\Type\ObjectType;
use Rector\NodeManipulator\ClassDependencyManipulator;
use Rector\PostRector\ValueObject\PropertyMetadata;
use Rector\Testing\PHPUnit\AbstractLazyTestCase;

final class ClassDependencyManipulatorTest extends AbstractLazyTestCase
{
private ClassDependencyManipulator $classDependencyManipulator;

private Standard $printerStandard;

protected function setUp(): void
{
$this->classDependencyManipulator = $this->make(ClassDependencyManipulator::class);
$this->printerStandard = new Standard();
}

public function testSingleMethod(): void
{
$someClass = new Class_(new Name('SingleMethodClass'));
$someClass->stmts[] = new ClassMethod('firstMethod');

$this->addSingleDependency($someClass);

$printedClass = $this->printerStandard->prettyPrintFile([$someClass]) . PHP_EOL;

$this->assertStringEqualsFile(__DIR__ . '/Fixture/expected_single_method.php.inc', $printedClass);
}

public function testWithProperty(): void
{
$someClass = new Class_(new Name('ClassWithSingleProperty'));
$someClass->stmts[] = new Property(Class_::MODIFIER_PRIVATE, [new PropertyProperty('someProperty')]);

$this->addSingleDependency($someClass);

$printedClass = $this->printerStandard->prettyPrintFile([$someClass]) . PHP_EOL;

$this->assertStringEqualsFile(__DIR__ . '/Fixture/expected_single_property.php.inc', $printedClass);
}

public function testWithMethodAndProperty(): void
{
$someClass = new Class_(new Name('ClassWithMethodAndProperty'));
$someClass->stmts[] = new Property(Class_::MODIFIER_PRIVATE, [new PropertyProperty('someProperty')]);
$someClass->stmts[] = new ClassMethod(new Identifier('someMethod'));

$this->addSingleDependency($someClass);

$printedClass = $this->printerStandard->prettyPrintFile([$someClass]) . PHP_EOL;
$this->assertStringEqualsFile(__DIR__ . '/Fixture/expected_method_and_property.php.inc', $printedClass);
}

private function addSingleDependency(Class_ $someClass): void
{
$this->classDependencyManipulator->addConstructorDependency($someClass, new PropertyMetadata(
'eventDispatcher',
new ObjectType('EventDispatcherInterface')
));
}
}
12 changes: 12 additions & 0 deletions tests/NodeManipulator/Fixture/expected_method_and_property.php.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

class ClassWithMethodAndProperty
{
private $someProperty;
public function __construct(private \EventDispatcherInterface $eventDispatcher)
{
}
function someMethod()
{
}
}
11 changes: 11 additions & 0 deletions tests/NodeManipulator/Fixture/expected_single_method.php.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

class SingleMethodClass
{
public function __construct(private \EventDispatcherInterface $eventDispatcher)
{
}
function firstMethod()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

class ClassWithSingleProperty
{
private $someProperty;
public function __construct(private \EventDispatcherInterface $eventDispatcher)
{
}
}

0 comments on commit dbd1baf

Please sign in to comment.