Skip to content

Commit

Permalink
[FEATURE] Add ExtbaseActionsWithRedirectMustReturnResponseInterfaceRe…
Browse files Browse the repository at this point in the history
…ctor

Resolves: #2863
Resolves: #4393
  • Loading branch information
simonschaufi committed Oct 25, 2024
1 parent 1021763 commit 6f6cb76
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 0 deletions.
2 changes: 2 additions & 0 deletions config/v12/typo3-120.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Ssch\TYPO3Rector\CodeQuality\General\RenameClassMapAliasRector;
use Ssch\TYPO3Rector\TYPO312\v0\AddMethodToWidgetInterfaceClassesRector;
use Ssch\TYPO3Rector\TYPO312\v0\ChangeExtbaseValidatorsRector;
use Ssch\TYPO3Rector\TYPO312\v0\ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector;
use Ssch\TYPO3Rector\TYPO312\v0\ImplementSiteLanguageAwareInterfaceRector;
use Ssch\TYPO3Rector\TYPO312\v0\MigrateBackendModuleRegistrationRector;
use Ssch\TYPO3Rector\TYPO312\v0\MigrateContentObjectRendererGetTypoLinkUrlRector;
Expand Down Expand Up @@ -171,4 +172,5 @@
$rectorConfig->rule(MigrateFetchToFetchAssociativeRector::class);
$rectorConfig->rule(MigrateBackendModuleRegistrationRector::class);
$rectorConfig->rule(MigrateContentObjectRendererGetTypoLinkUrlRector::class);
$rectorConfig->rule(ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector::class);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

declare(strict_types=1);

namespace Ssch\TYPO3Rector\TYPO312\v0;

use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ObjectType;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @changelog https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/12.0/Breaking-96107-DeprecatedFunctionalityRemoved.html
* @see \Ssch\TYPO3Rector\Tests\Rector\v12\v0\ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector\ExtbaseActionsWithRedirectMustReturnResponseInterfaceRectorTest
*/
final class ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector extends AbstractRector
{
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [ClassMethod::class];
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Extbase controller actions with redirects must return ResponseInterface', [
new CodeSample(
<<<'CODE_SAMPLE'
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
public function someAction()
{
$this->redirect('foo', 'bar');
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class MyController extends ActionController
{
public function someAction(): ResponseInterface
{
return $this->redirect('foo', 'bar');
}
}
CODE_SAMPLE
),
]);
}

/**
* @param ClassMethod $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->nodeTypeResolver->isMethodStaticCallOrClassMethodObjectType(
$node,
new ObjectType('TYPO3\CMS\Extbase\Mvc\Controller\ActionController')
)) {
return null;
}

$hasChanged = false;

$this->traverseNodesWithCallable($node, function (Node $node) use (&$hasChanged) {
if (! $node instanceof Expression) {
return null;
}

$methodCall = $node->expr;
if (! $methodCall instanceof MethodCall) {
return null;
}

if (! $this->isNames($methodCall->name, ['redirect', 'redirectToUri'])) {
return null;
}

$returnMethodCall = new Return_($methodCall);
$hasChanged = true;
return $returnMethodCall;
});

if (! $hasChanged) {
return null;
}

$this->changeActionMethodReturnTypeIfPossible($node);

return $node;
}

private function changeActionMethodReturnTypeIfPossible(ClassMethod $actionMethodNode): void
{
if ($actionMethodNode->returnType instanceof Identifier
&& $actionMethodNode->returnType->name !== null
&& $actionMethodNode->returnType->name === 'void'
) {
$actionMethodNode->returnType = null;
}

$comments = $actionMethodNode->getComments();
$comments = array_map(
static fn (Comment $comment) => new Comment(str_replace(
'@return void',
'@return \Psr\Http\Message\ResponseInterface',
$comment->getText()
)),
$comments
);
$actionMethodNode->setAttribute(AttributeKey::COMMENTS, $comments);

// Add returnType only if it is the only statement, otherwise it is not reliable
if (is_countable($actionMethodNode->stmts) && count((array) $actionMethodNode->stmts) === 1) {
$actionMethodNode->returnType = new FullyQualified('Psr\Http\Message\ResponseInterface');
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Ssch\TYPO3Rector\Tests\Rector\v12\v0\ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector;

use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class ExtbaseActionsWithRedirectMustReturnResponseInterfaceRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

/**
* @return \Iterator<array<string>>
*/
public static function provideData(): \Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

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

namespace Ssch\TYPO3Rector\Tests\Rector\v12\v0\ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector\Fixture;

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class MyController extends ActionController
{
public function handleExpiredRegistrationsAction()
{
$this->redirect('list');
}

public function imageDeleteAction()
{
$this->redirectToUri('foo');
}

/**
* @return void
*/
public function imageDeleteActionWithReturnVoid(): void
{
$this->redirectToUri('foo');
}

public function alreadyMigratedHandleExpiredRegistrationsAction(): ResponseInterface
{
return $this->redirect('list');
}

public function alreadyMigratedImageDeleteAction(): ResponseInterface
{
return $this->redirectToUri('foo');
}
}

?>
-----
<?php

namespace Ssch\TYPO3Rector\Tests\Rector\v12\v0\ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector\Fixture;

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class MyController extends ActionController
{
public function handleExpiredRegistrationsAction(): ResponseInterface
{
return $this->redirect('list');
}

public function imageDeleteAction(): ResponseInterface
{
return $this->redirectToUri('foo');
}

/**
* @return \Psr\Http\Message\ResponseInterface
*/
public function imageDeleteActionWithReturnVoid(): ResponseInterface
{
return $this->redirectToUri('foo');
}

public function alreadyMigratedHandleExpiredRegistrationsAction(): ResponseInterface
{
return $this->redirect('list');
}

public function alreadyMigratedImageDeleteAction(): ResponseInterface
{
return $this->redirectToUri('foo');
}
}

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

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Ssch\TYPO3Rector\TYPO312\v0\ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->import(__DIR__ . '/../../../../../../config/config_test.php');
$rectorConfig->rule(ExtbaseActionsWithRedirectMustReturnResponseInterfaceRector::class);
};

0 comments on commit 6f6cb76

Please sign in to comment.