mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-24 19:53:14 +01:00
Merge pull request #3654 from rectorphp/template-fix
[Sension] improve template annotation
This commit is contained in:
commit
771b2dc85d
@ -335,3 +335,5 @@ parameters:
|
|||||||
- '#Parameter \#1 \$expr of method Rector\\MagicDisclosure\\Matcher\\ClassNameTypeMatcher\:\:doesExprMatchNames\(\) expects PhpParser\\Node\\Expr, PhpParser\\Node\\Expr\|null given#'
|
- '#Parameter \#1 \$expr of method Rector\\MagicDisclosure\\Matcher\\ClassNameTypeMatcher\:\:doesExprMatchNames\(\) expects PhpParser\\Node\\Expr, PhpParser\\Node\\Expr\|null given#'
|
||||||
- '#Parameter \#1 \$objectType of method Rector\\Naming\\Naming\\PropertyNaming\:\:fqnToVariableName\(\) expects PHPStan\\Type\\ObjectType\|string, PHPStan\\Type\\Type given#'
|
- '#Parameter \#1 \$objectType of method Rector\\Naming\\Naming\\PropertyNaming\:\:fqnToVariableName\(\) expects PHPStan\\Type\\ObjectType\|string, PHPStan\\Type\\Type given#'
|
||||||
- '#Method Rector\\Core\\PhpParser\\Node\\NodeFactory\:\:createConcat\(\) should return PhpParser\\Node\\Expr\\BinaryOp\\Concat\|null but returns PhpParser\\Node\\Expr#'
|
- '#Method Rector\\Core\\PhpParser\\Node\\NodeFactory\:\:createConcat\(\) should return PhpParser\\Node\\Expr\\BinaryOp\\Concat\|null but returns PhpParser\\Node\\Expr#'
|
||||||
|
- '#Method Rector\\Core\\PhpParser\\Node\\BetterNodeFinder\:\:findFirstNonAnonymousClass\(\) should return PhpParser\\Node\\Stmt\\Class_\|null but returns PhpParser\\Node\|null#'
|
||||||
|
|
||||||
|
113
rules/sensio/src/BundleClassResolver.php
Normal file
113
rules/sensio/src/BundleClassResolver.php
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Sensio;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeTraverser;
|
||||||
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
|
use Rector\CodingStyle\Naming\ClassNaming;
|
||||||
|
use Rector\Core\PhpParser\Node\BetterNodeFinder;
|
||||||
|
use Rector\Core\PhpParser\Parser\Parser;
|
||||||
|
use Rector\NodeNameResolver\NodeNameResolver;
|
||||||
|
use ReflectionClass;
|
||||||
|
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||||
|
|
||||||
|
final class BundleClassResolver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Parser
|
||||||
|
*/
|
||||||
|
private $parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var BetterNodeFinder
|
||||||
|
*/
|
||||||
|
private $betterNodeFinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ClassNaming
|
||||||
|
*/
|
||||||
|
private $classNaming;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var NodeNameResolver
|
||||||
|
*/
|
||||||
|
private $nodeNameResolver;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Parser $parser,
|
||||||
|
BetterNodeFinder $betterNodeFinder,
|
||||||
|
ClassNaming $classNaming,
|
||||||
|
NodeNameResolver $nodeNameResolver
|
||||||
|
) {
|
||||||
|
$this->parser = $parser;
|
||||||
|
$this->betterNodeFinder = $betterNodeFinder;
|
||||||
|
$this->classNaming = $classNaming;
|
||||||
|
$this->nodeNameResolver = $nodeNameResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveShortBundleClassFromControllerClass(string $class): ?string
|
||||||
|
{
|
||||||
|
// resolve bundle from existing ones
|
||||||
|
$classReflection = new ReflectionClass($class);
|
||||||
|
|
||||||
|
$fileName = $classReflection->getFileName();
|
||||||
|
if (! $fileName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$controllerDirectory = dirname($fileName);
|
||||||
|
|
||||||
|
$rootFolder = getenv('SystemDrive', true) . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
// traverse up, un-till first bundle class appears
|
||||||
|
$bundleFiles = [];
|
||||||
|
while ($bundleFiles === [] && $controllerDirectory !== $rootFolder) {
|
||||||
|
$bundleFiles = (array) glob($controllerDirectory . '/**Bundle.php');
|
||||||
|
$controllerDirectory = dirname($controllerDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($bundleFiles) === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string $bundleFile */
|
||||||
|
$bundleFile = $bundleFiles[0];
|
||||||
|
|
||||||
|
$bundleClassName = $this->resolveClassNameFromFilePath($bundleFile);
|
||||||
|
if ($bundleClassName !== null) {
|
||||||
|
return $this->classNaming->getShortName($bundleClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveClassNameFromFilePath(string $filePath): ?string
|
||||||
|
{
|
||||||
|
$fileInfo = new SmartFileInfo($filePath);
|
||||||
|
$nodes = $this->parser->parseFileInfo($fileInfo);
|
||||||
|
|
||||||
|
$this->addFullyQualifiedNamesToNodes($nodes);
|
||||||
|
|
||||||
|
$class = $this->betterNodeFinder->findFirstNonAnonymousClass($nodes);
|
||||||
|
if ($class === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->nodeNameResolver->getName($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node[] $nodes
|
||||||
|
*/
|
||||||
|
private function addFullyQualifiedNamesToNodes(array $nodes): void
|
||||||
|
{
|
||||||
|
$nodeTraverser = new NodeTraverser();
|
||||||
|
$nameResolverNodeVisitor = new NameResolver();
|
||||||
|
$nodeTraverser->addVisitor($nameResolverNodeVisitor);
|
||||||
|
|
||||||
|
$nodeTraverser->traverse($nodes);
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,10 @@ use PhpParser\Node\Stmt\ClassMethod;
|
|||||||
use Rector\Core\Exception\ShouldNotHappenException;
|
use Rector\Core\Exception\ShouldNotHappenException;
|
||||||
use Rector\NodeNameResolver\NodeNameResolver;
|
use Rector\NodeNameResolver\NodeNameResolver;
|
||||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||||
|
use Rector\Sensio\BundleClassResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see \Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationRector\TemplateAnnotationRectorTest
|
* @see \Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationToThisRenderRector\TemplateAnnotationToThisRenderRectorTest
|
||||||
*/
|
*/
|
||||||
final class TemplateGuesser
|
final class TemplateGuesser
|
||||||
{
|
{
|
||||||
@ -20,9 +21,15 @@ final class TemplateGuesser
|
|||||||
*/
|
*/
|
||||||
private $nodeNameResolver;
|
private $nodeNameResolver;
|
||||||
|
|
||||||
public function __construct(NodeNameResolver $nodeNameResolver)
|
/**
|
||||||
|
* @var BundleClassResolver
|
||||||
|
*/
|
||||||
|
private $bundleClassResolver;
|
||||||
|
|
||||||
|
public function __construct(NodeNameResolver $nodeNameResolver, BundleClassResolver $bundleClassResolver)
|
||||||
{
|
{
|
||||||
$this->nodeNameResolver = $nodeNameResolver;
|
$this->nodeNameResolver = $nodeNameResolver;
|
||||||
|
$this->bundleClassResolver = $bundleClassResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolveFromClassMethodNode(ClassMethod $classMethod): string
|
public function resolveFromClassMethodNode(ClassMethod $classMethod): string
|
||||||
@ -50,17 +57,24 @@ final class TemplateGuesser
|
|||||||
*/
|
*/
|
||||||
private function resolve(string $namespace, string $class, string $method): string
|
private function resolve(string $namespace, string $class, string $method): string
|
||||||
{
|
{
|
||||||
$bundle = Strings::match($namespace, '#(?<bundle>[\w]*Bundle)#')['bundle'] ?? '';
|
$bundle = $this->resolveBundle($class, $namespace);
|
||||||
$bundle = Strings::replace($bundle, '#Bundle$#');
|
$controller = $this->resolveController($class);
|
||||||
$bundle = $bundle !== '' ? '@' . $bundle . '/' : '';
|
|
||||||
|
|
||||||
$controller = $this->resolveControllerVersion5($class);
|
|
||||||
$action = Strings::replace($method, '#Action$#');
|
$action = Strings::replace($method, '#Action$#');
|
||||||
|
|
||||||
return sprintf('%s%s%s.html.twig', $bundle, $controller, $action);
|
$fullPath = '';
|
||||||
|
if ($bundle !== '') {
|
||||||
|
$fullPath .= $bundle . '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($controller !== '') {
|
||||||
|
$fullPath .= $controller . '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fullPath . $action . '.html.twig';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveControllerVersion5(string $class): string
|
private function resolveController(string $class): string
|
||||||
{
|
{
|
||||||
$match = Strings::match($class, '#Controller\\\(.+)Controller$#');
|
$match = Strings::match($class, '#Controller\\\(.+)Controller$#');
|
||||||
if (! $match) {
|
if (! $match) {
|
||||||
@ -68,9 +82,18 @@ final class TemplateGuesser
|
|||||||
}
|
}
|
||||||
|
|
||||||
$controller = Strings::replace($match[1], '#([a-z\d])([A-Z])#', '\\1_\\2');
|
$controller = Strings::replace($match[1], '#([a-z\d])([A-Z])#', '\\1_\\2');
|
||||||
$controller = strtolower($controller);
|
return str_replace('\\', '/', $controller);
|
||||||
$controller = str_replace('\\', '/', $controller);
|
}
|
||||||
|
|
||||||
return $controller !== '' ? $controller . '/' : '';
|
private function resolveBundle(string $class, string $namespace): string
|
||||||
|
{
|
||||||
|
$shortBundleClass = $this->bundleClassResolver->resolveShortBundleClassFromControllerClass($class);
|
||||||
|
if ($shortBundleClass !== null) {
|
||||||
|
return '@' . $shortBundleClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bundle = Strings::match($namespace, '#(?<bundle>[\w]*Bundle)#')['bundle'] ?? '';
|
||||||
|
$bundle = Strings::replace($bundle, '#Bundle$#');
|
||||||
|
return $bundle !== '' ? '@' . $bundle : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationToThisRenderRector;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
use Nette\Utils\FileSystem;
|
||||||
|
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
|
||||||
|
use Rector\Sensio\Rector\FrameworkExtraBundle\TemplateAnnotationToThisRenderRector;
|
||||||
|
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||||
|
|
||||||
|
final class DifferentBundleNameRectorTest extends AbstractRectorTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideData()
|
||||||
|
*/
|
||||||
|
public function test(SmartFileInfo $fileInfo): void
|
||||||
|
{
|
||||||
|
// prepare bundle path
|
||||||
|
$originalBundleFilePath = __DIR__ . '/FixtureDifferentBundleName/SomeActionBundle/DifferentNameBundle.php';
|
||||||
|
$temporaryBundleFilePath = $this->getTempPath() . '/DifferentNameBundle.php';
|
||||||
|
FileSystem::copy($originalBundleFilePath, $temporaryBundleFilePath, true);
|
||||||
|
|
||||||
|
$this->doTestFileInfo($fileInfo);
|
||||||
|
|
||||||
|
FileSystem::delete($temporaryBundleFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideData(): Iterator
|
||||||
|
{
|
||||||
|
return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureDifferentBundleName');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRectorClass(): string
|
||||||
|
{
|
||||||
|
return TemplateAnnotationToThisRenderRector::class;
|
||||||
|
}
|
||||||
|
}
|
@ -51,7 +51,7 @@ final class ClassWithArrayAndManyResponseController extends AbstractController
|
|||||||
if ($responseOrData instanceof \Symfony\Component\HttpFoundation\Response) {
|
if ($responseOrData instanceof \Symfony\Component\HttpFoundation\Response) {
|
||||||
return $responseOrData;
|
return $responseOrData;
|
||||||
}
|
}
|
||||||
return $this->render('@App/class_with_array_and_many_response/index.html.twig', $responseOrData);
|
return $this->render('@App/Class_With_Array_And_Many_Response/index.html.twig', $responseOrData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,12 +45,12 @@ class ClassWithNamedService15Controller extends AbstractController
|
|||||||
{
|
{
|
||||||
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
return $this->render('@App/class_with_named_service15/index.html.twig');
|
return $this->render('@App/Class_With_Named_Service15/index.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index2Action(): \Symfony\Component\HttpFoundation\Response
|
public function index2Action(): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
return $this->render('@App/class_with_named_service15/index2.html.twig', ['someKey' => 'someValue']);
|
return $this->render('@App/Class_With_Named_Service15/index2.html.twig', ['someKey' => 'someValue']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index3Action(): \Symfony\Component\HttpFoundation\Response
|
public function index3Action(): \Symfony\Component\HttpFoundation\Response
|
||||||
|
@ -28,7 +28,7 @@ class EmptyBodyController extends AbstractController
|
|||||||
{
|
{
|
||||||
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
return $this->render('@App/empty_body/index.html.twig');
|
return $this->render('@App/Empty_Body/index.html.twig');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class ClassWithNamedService35Controller extends AbstractController
|
|||||||
return $this->redirectToRoute('rector_is_cool');
|
return $this->redirectToRoute('rector_is_cool');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('@App/class_with_named_service35/index.html.twig', array(
|
return $this->render('@App/Class_With_Named_Service35/index.html.twig', array(
|
||||||
'form' => $form->createView()
|
'form' => $form->createView()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class ClassWithNamedService55Controller extends AbstractController
|
|||||||
{
|
{
|
||||||
public function index(): \Symfony\Component\HttpFoundation\Response
|
public function index(): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
return $this->render('class_with_named_service55/index.html.twig');
|
return $this->render('Class_With_Named_Service55/index.html.twig');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +44,11 @@ class ClassIfElseArrayController extends AbstractController
|
|||||||
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
if (mt_rand(0, 100)) {
|
if (mt_rand(0, 100)) {
|
||||||
return $this->render('@App/class_if_else_array/index.html.twig', [
|
return $this->render('@App/Class_If_Else_Array/index.html.twig', [
|
||||||
'key' => 'value'
|
'key' => 'value'
|
||||||
]);
|
]);
|
||||||
} elseif (mt_rand(0, 200)) {
|
} elseif (mt_rand(0, 200)) {
|
||||||
return $this->render('@App/class_if_else_array/index.html.twig', [
|
return $this->render('@App/Class_If_Else_Array/index.html.twig', [
|
||||||
'key' => 'value2'
|
'key' => 'value2'
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,7 +40,7 @@ class ClassTryCatchArrayResponseController extends AbstractController
|
|||||||
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return $this->render('@App/class_try_catch_array_response/index.html.twig', [
|
return $this->render('@App/Class_Try_Catch_Array_Response/index.html.twig', [
|
||||||
'key' => 'value'
|
'key' => 'value'
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $throwable) {
|
} catch (Throwable $throwable) {
|
||||||
|
@ -28,7 +28,7 @@ class WithOnlyVarsController extends AbstractController
|
|||||||
{
|
{
|
||||||
public function index(Post $post): \Symfony\Component\HttpFoundation\Response
|
public function index(Post $post): \Symfony\Component\HttpFoundation\Response
|
||||||
{
|
{
|
||||||
return $this->render('with_only_vars/index.html.twig', ['post' => $post]);
|
return $this->render('With_Only_Vars/index.html.twig', ['post' => $post]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationToThisRenderRector\FixtureDifferentBundleName\SomeActionBundle\Controller;
|
||||||
|
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
|
||||||
|
class BasicController extends AbstractController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Template()
|
||||||
|
*/
|
||||||
|
public function someAction()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationToThisRenderRector\FixtureDifferentBundleName\SomeActionBundle\Controller;
|
||||||
|
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
|
||||||
|
class BasicController extends AbstractController
|
||||||
|
{
|
||||||
|
public function someAction(): \Symfony\Component\HttpFoundation\Response
|
||||||
|
{
|
||||||
|
return $this->render('@DifferentNameBundle/Basic/some.html.twig');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationToThisRenderRector\FixtureDifferentBundleName\SomeActionBundle;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
|
||||||
|
final class DifferentNameBundle extends Bundle
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
some view
|
@ -162,6 +162,21 @@ final class BetterNodeFinder
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node[] $nodes
|
||||||
|
*/
|
||||||
|
public function findFirstNonAnonymousClass(array $nodes): ?Class_
|
||||||
|
{
|
||||||
|
return $this->findFirst($nodes, function (Node $node): bool {
|
||||||
|
if (! $node instanceof ClassLike) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip anonymous classes
|
||||||
|
return ! ($node instanceof Class_ && $node->isAnonymous());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Node|Node[] $nodes
|
* @param Node|Node[] $nodes
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user