mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-24 11:44: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 \$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\\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\NodeNameResolver\NodeNameResolver;
|
||||
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
|
||||
{
|
||||
@ -20,9 +21,15 @@ final class TemplateGuesser
|
||||
*/
|
||||
private $nodeNameResolver;
|
||||
|
||||
public function __construct(NodeNameResolver $nodeNameResolver)
|
||||
/**
|
||||
* @var BundleClassResolver
|
||||
*/
|
||||
private $bundleClassResolver;
|
||||
|
||||
public function __construct(NodeNameResolver $nodeNameResolver, BundleClassResolver $bundleClassResolver)
|
||||
{
|
||||
$this->nodeNameResolver = $nodeNameResolver;
|
||||
$this->bundleClassResolver = $bundleClassResolver;
|
||||
}
|
||||
|
||||
public function resolveFromClassMethodNode(ClassMethod $classMethod): string
|
||||
@ -50,17 +57,24 @@ final class TemplateGuesser
|
||||
*/
|
||||
private function resolve(string $namespace, string $class, string $method): string
|
||||
{
|
||||
$bundle = Strings::match($namespace, '#(?<bundle>[\w]*Bundle)#')['bundle'] ?? '';
|
||||
$bundle = Strings::replace($bundle, '#Bundle$#');
|
||||
$bundle = $bundle !== '' ? '@' . $bundle . '/' : '';
|
||||
$bundle = $this->resolveBundle($class, $namespace);
|
||||
$controller = $this->resolveController($class);
|
||||
|
||||
$controller = $this->resolveControllerVersion5($class);
|
||||
$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$#');
|
||||
if (! $match) {
|
||||
@ -68,9 +82,18 @@ final class TemplateGuesser
|
||||
}
|
||||
|
||||
$controller = Strings::replace($match[1], '#([a-z\d])([A-Z])#', '\\1_\\2');
|
||||
$controller = strtolower($controller);
|
||||
$controller = str_replace('\\', '/', $controller);
|
||||
return 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) {
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
|
@ -28,7 +28,7 @@ class EmptyBodyController extends AbstractController
|
||||
{
|
||||
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->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()
|
||||
));
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class ClassWithNamedService55Controller extends AbstractController
|
||||
{
|
||||
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
|
||||
{
|
||||
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'
|
||||
]);
|
||||
} 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'
|
||||
]);
|
||||
} else {
|
||||
|
@ -40,7 +40,7 @@ class ClassTryCatchArrayResponseController extends AbstractController
|
||||
public function indexAction(): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
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'
|
||||
]);
|
||||
} catch (Throwable $throwable) {
|
||||
|
@ -28,7 +28,7 @@ class WithOnlyVarsController extends AbstractController
|
||||
{
|
||||
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
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user