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,19 @@
<?php

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\Fixture;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

final class SkipReturnInClosureOnly extends AbstractController
{
#[Route]
public function detail()
{
$callback = function () {
return 'hey';
};

$callback();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\FixtureTypeGuarded;

use Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\Source\AbstractCustomController;
use Symfony\Component\Routing\Annotation\Route;

final class FinalController extends AbstractCustomController
{
#[Route]
public function detail()
{
return $this->render('some_template');
}
}

?>
-----
<?php

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\FixtureTypeGuarded;

use Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\Source\AbstractCustomController;
use Symfony\Component\Routing\Annotation\Route;

final class FinalController extends AbstractCustomController
{
#[Route]
public function detail(): \Symfony\Component\HttpFoundation\Response
{
return $this->render('some_template');
}
}

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

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\FixtureTypeGuarded;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class NonGuardedController extends AbstractController
{
#[Route]
public function detail()
{
return $this->render('some_template');
}
}

?>
-----
<?php

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\FixtureTypeGuarded;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class NonGuardedController extends AbstractController
{
#[Route]
public function detail(): \Symfony\Component\HttpFoundation\Response
{
return $this->render('some_template');
}
}

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

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\FixtureTypeGuarded;

use Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\Source\AbstractCustomController;
use Symfony\Component\Routing\Annotation\Route;

class SkipTypeGuardedController extends AbstractCustomController
{
#[Route]
public function detail()
{
return $this->render('some_template');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class ResponseReturnTypeControllerActionRectorTypeGuardedTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/FixtureTypeGuarded');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/type_guarded_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\Source;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

abstract class AbstractCustomController extends AbstractController
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Symfony\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector;
use Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\ResponseReturnTypeControllerActionRector\Source\AbstractCustomController;

return RectorConfig::configure()
->withRules([ResponseReturnTypeControllerActionRector::class])
->withTypeGuardedClasses([AbstractCustomController::class]);
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use Rector\TypeDeclaration\NodeAnalyzer\ReturnAnalyzer;
use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VendorLocker\ParentClassMethodTypeOverrideGuard;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
Expand All @@ -45,7 +46,8 @@ public function __construct(
private readonly BetterNodeFinder $betterNodeFinder,
private readonly ReturnAnalyzer $returnAnalyzer,
private readonly StaticTypeMapper $staticTypeMapper,
private readonly ReturnTypeInferer $returnTypeInferer
private readonly ReturnTypeInferer $returnTypeInferer,
private readonly ParentClassMethodTypeOverrideGuard $parentClassMethodTypeOverrideGuard
) {
}

Expand Down Expand Up @@ -112,6 +114,11 @@ public function refactor(Node $node): ?Node
return null;
}

// adding a return type would break child classes of user-guarded classes
if ($this->parentClassMethodTypeOverrideGuard->isTypeGuardedClass($node)) {
return null;
}

if (! $this->isActionClassMethod($node)) {
return null;
}
Expand Down Expand Up @@ -188,7 +195,7 @@ private function isActionClassMethod(ClassMethod $classMethod): bool

private function hasReturn(ClassMethod $classMethod): bool
{
return $this->betterNodeFinder->hasInstancesOf($classMethod, [Return_::class]);
return $this->betterNodeFinder->hasInstancesOfInFunctionLikeScoped($classMethod, Return_::class);
}

private function refactorResponse(ClassMethod $classMethod): ?ClassMethod
Expand Down
Loading