add FQN route + check for existing one

This commit is contained in:
Tomas Votruba 2019-02-02 16:52:24 +01:00
parent a9338ab9c8
commit c29c09348a
10 changed files with 64 additions and 36 deletions

View File

@ -40,7 +40,7 @@ final class RouteTagValueNode implements PhpDocChildNode
public function __toString(): string
{
$string = sprintf('@%s(', $this->routeClass);
$string = sprintf('@\\%s(', $this->routeClass);
$string .= sprintf('path="%s"', $this->path);
if ($this->name) {

View File

@ -7,6 +7,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\NetteToSymfony\Annotation\RouteTagValueNode;
use Rector\NetteToSymfony\Route\RouteInfo;
@ -19,6 +20,7 @@ use Rector\PhpParser\Node\Maintainer\ClassMethodMaintainer;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\Util\RectorStrings;
use ReflectionMethod;
/**
@ -275,36 +277,22 @@ CODE_SAMPLE
foreach ($presenterClassNodes as $presenterClassNode) {
foreach ((array) $presenterClassNode->stmts as $classStmt) {
if (! $classStmt instanceof ClassMethod) {
if ($this->shouldSkipClassStmt($classStmt)) {
continue;
}
// not an action method
if (! $classStmt->isPublic()) {
continue;
}
/** @var ClassMethod $classStmt */
$path = $this->resolvePathFromClassAndMethodNodes($presenterClassNode, $classStmt);
$phpDocTagNode = new RouteTagValueNode($this->routeAnnotationClass, $path);
if (! $this->matchName($classStmt, '#^(render|action)#')) {
continue;
}
// already has Route tag
if ($this->docBlockAnalyzer->hasTag($classStmt, $this->routerClass)) {
continue;
}
$presenterName = $this->getName($presenterClassNode);
$shortPresenterName = Strings::after($presenterName, '\\', -1);
die;
dump($this->getName($classStmt));
die;
$this->docBlockAnalyzer->addTag($classStmt, $phpDocTagNode);
}
}
die;
}
/**
* @todo allow extension with custom resolvers
*/
private function isRouteStaticCallMatch(StaticCall $node): bool
{
$className = $this->getName($node->class);
@ -332,4 +320,36 @@ CODE_SAMPLE
return false;
}
private function shouldSkipClassStmt(Node $node): bool
{
if (! $node instanceof ClassMethod) {
return true;
}
// not an action method
if (! $node->isPublic()) {
return true;
}
if (! $this->matchName($node, '#^(render|action)#')) {
return true;
}
// already has Route tag
return $this->docBlockAnalyzer->hasTag($node, $this->routeAnnotationClass);
}
private function resolvePathFromClassAndMethodNodes(Class_ $classNode, ClassMethod $classMethodNode): string
{
$presenterName = $this->getName($classNode);
$presenterPart = Strings::after($presenterName, '\\', -1);
$presenterPart = Strings::substring($presenterPart, 0, -Strings::length('Presenter'));
$presenterPart = RectorStrings::camelCaseToDashes($presenterPart);
$match = Strings::match($this->getName($classMethodNode), '#^(action|render)(?<short_action_name>.*?$)#sm');
$actionPart = lcfirst($match['short_action_name']);
return $presenterPart . '/' . $actionPart;
}
}

View File

@ -51,7 +51,7 @@ final class ConstantReferenceRouterFactory
final class ConstantReferenceSomePresenter
{
/**
* @Symfony\Component\Routing\Annotation\Route(path="/some-path", methods={"GET"})
* @\Symfony\Component\Routing\Annotation\Route(path="/some-path", methods={"GET"})
*/
public function run()
{

View File

@ -41,7 +41,6 @@ final class GeneralMethodNamedRoutesRouterFactory
public function create(): RouteList
{
$routeList = new RouteList();
$routeList[] = new Route('<presenter>/<action>', 'Homepage:default');
return $routeList;
}
@ -50,14 +49,14 @@ final class GeneralMethodNamedRoutesRouterFactory
final class GeneralMethodNamedRoutesSomePresenter
{
/**
* @Symfony\Component\Routing\Annotation\Route(path="general-method-named-routes-some/first")
* @\Symfony\Component\Routing\Annotation\Route(path="general-method-named-routes-some/first")
*/
public function actionFirst()
{
}
/**
* @Symfony\Component\Routing\Annotation\Route(path="general-method-named-routes-some/second")
* @\Symfony\Component\Routing\Annotation\Route(path="general-method-named-routes-some/second")
*/
public function actionSecond()
{

View File

@ -51,14 +51,14 @@ final class MethodNamedRoutesRouterFactory
final class MethodNamedRoutesSomePresenter
{
/**
* @Symfony\Component\Routing\Annotation\Route(path="hi")
* @\Symfony\Component\Routing\Annotation\Route(path="hi")
*/
public function actionFirst()
{
}
/**
* @Symfony\Component\Routing\Annotation\Route(path="hello")
* @\Symfony\Component\Routing\Annotation\Route(path="hello")
*/
public function actionSecond()
{

View File

@ -47,7 +47,7 @@ final class NewRouterFactory
final class NewSomePresenter
{
/**
* @Symfony\Component\Routing\Annotation\Route(path="some-path")
* @\Symfony\Component\Routing\Annotation\Route(path="some-path")
*/
public function run()
{

View File

@ -47,7 +47,7 @@ final class StaticRouterFactory
final class StaticSomePresenter
{
/**
* @Symfony\Component\Routing\Annotation\Route(path="some-path", methods={"GET"})
* @\Symfony\Component\Routing\Annotation\Route(path="some-path", methods={"GET"})
*/
public function run()
{

View File

@ -12,10 +12,10 @@ final class RouterListToControllerAnnotationsRetorTest extends AbstractRectorTes
public function test(): void
{
$this->doTestFiles([
// __DIR__ . '/Fixture/new_route_to_annotation.php.inc',
// __DIR__ . '/Fixture/static_route_to_annotation.php.inc',
// __DIR__ . '/Fixture/constant_reference_route_to_annotation.php.inc',
// __DIR__ . '/Fixture/method_named_routes.php.inc',
__DIR__ . '/Fixture/new_route_to_annotation.php.inc',
__DIR__ . '/Fixture/static_route_to_annotation.php.inc',
__DIR__ . '/Fixture/constant_reference_route_to_annotation.php.inc',
__DIR__ . '/Fixture/method_named_routes.php.inc',
__DIR__ . '/Fixture/general_method_named_routes.php.inc',
]);
}

View File

@ -68,8 +68,16 @@ final class DocBlockAnalyzer
return false;
}
// normalize tag name
$name = ltrim($name, '@');
// simple check
if (Strings::contains($node->getDocComment()->getText(), '@' . $name)) {
if (Strings::contains($node->getDocComment()->getText(), '@' . ltrim($name, '@'))) {
return true;
}
// fqn class annotation
if (Strings::contains($node->getDocComment()->getText(), '@\\' . ltrim($name, '@'))) {
return true;
}

View File

@ -29,6 +29,7 @@ parameters:
- '#Call to function method_exists\(\) with string and (.*?) will always evaluate to false#'
- '#PHPDoc tag \@param for parameter \$node with type float is incompatible with native type PhpParser\\Node#'
- '#Result of && is always true#'
- '#Parameter \#2 \$classMethodNode of method Rector\\NetteToSymfony\\Rector\\RouterListToControllerAnnotationsRector\:\:resolvePathFromClassAndMethodNodes\(\) expects PhpParser\\Node\\Stmt\\ClassMethod, PhpParser\\Node\\Stmt given#'
# missuse of interface and class
- '#Parameter \#1 (.*?) expects Symfony\\Component\\DependencyInjection\\ContainerBuilder, Symfony\\Component\\DependencyInjection\\ContainerInterface given#'