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
@@ -1,10 +1,6 @@
# from: https://github.com/Kdyby/Events/
# to: https://github.com/contributte/event-dispatcher/
services:
Rector\Renaming\Rector\Class_\RenameClassRector:
$oldToNewClasses:
Kdyby\Events\Subscriber: 'Symfony\Component\EventDispatcher\EventSubscriberInterface'

Rector\NetteKdyby\Rector\Class_\KdybyEventSubscriberToContributteEventSubscriberRector: null
Rector\NetteKdyby\Rector\MethodCall\ReplaceMagicPropertyEventWithEventClassRector: null
Rector\NetteKdyby\Rector\ClassMethod\ReplaceMagicEventPropertySubscriberWithEventClassSubscriberRector: null
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,4 @@ parameters:

- '#Cognitive complexity for "Rector\\TypeDeclaration\\PHPStan\\Type\\ObjectTypeSpecifier\:\:matchShortenedObjectType\(\)" is 10, keep it under 9#'
- '#Parameter \#1 \$objectType of method Rector\\Core\\Naming\\PropertyNaming\:\:fqnToVariableName\(\) expects PHPStan\\Type\\ObjectType\|string, PHPStan\\Type\\Type given#'
- '#Parameter \#1 \$type of method PhpParser\\Builder\\FunctionLike\:\:setReturnType\(\) expects PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|string, PhpParser\\Node\\Identifier\|PhpParser\\Node\\Name\|PhpParser\\Node\\NullableType\|PhpParser\\Node\\UnionType given#'
1 change: 1 addition & 0 deletions rules/nette-kdyby/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ services:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'
- '../src/ValueObject/*'
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace Rector\NetteKdyby\BlueprintFactory;

use PhpParser\Node\Arg;
use Rector\NetteKdyby\Naming\VariableNaming;
use Rector\NetteKdyby\ValueObject\VariableWithType;
use Rector\NodeTypeResolver\NodeTypeResolver;
use Rector\StaticTypeMapper\StaticTypeMapper;

final class VariableWithTypesFactory
{
/**
* @var VariableNaming
*/
private $variableNaming;

/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;

/**
* @var StaticTypeMapper
*/
private $staticTypeMapper;

public function __construct(
VariableNaming $variableNaming,
NodeTypeResolver $nodeTypeResolver,
StaticTypeMapper $staticTypeMapper
) {
$this->variableNaming = $variableNaming;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->staticTypeMapper = $staticTypeMapper;
}

/**
* @param Arg[] $args
* @return VariableWithType[]
*/
public function createVariablesWithTypesFromArgs(array $args): array
{
$variablesWithTypes = [];

foreach ($args as $arg) {
$variableName = $this->variableNaming->resolveParamNameFromArg($arg);
$staticType = $this->nodeTypeResolver->getStaticType($arg->value);
$phpParserTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($staticType);

$variablesWithTypes[] = new VariableWithType($variableName, $staticType, $phpParserTypeNode);
}

return $variablesWithTypes;
}
}
98 changes: 98 additions & 0 deletions rules/nette-kdyby/src/Naming/VariableNaming.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace Rector\NetteKdyby\Naming;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Scalar\String_;
use Rector\Core\Exception\NotImplementedException;
use Rector\NodeNameResolver\NodeNameResolver;

final class VariableNaming
{
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;

public function __construct(NodeNameResolver $nodeNameResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
}

public function resolveParamNameFromArg(Arg $arg): string
{
$value = $arg->value;

if ($value instanceof Cast) {
$value = $value->expr;
}

if ($value instanceof Ternary) {
$value = $value->if;
}

while ($value instanceof ArrayDimFetch) {
$value = $value->var;
}

if ($value instanceof PropertyFetch) {
return $this->resolveParamNameFromPropertyFetch($value);
}

if ($value instanceof MethodCall) {
return $this->resolveParamNameFromMethodCall($value);
}

if ($value === null) {
throw new NotImplementedException();
}

$paramName = $this->nodeNameResolver->getName($value);
if ($paramName !== null) {
return $paramName;
}

if ($value instanceof String_) {
return $value->value;
}

throw new NotImplementedException();
}

private function resolveParamNameFromPropertyFetch(PropertyFetch $propertyFetch): string
{
$varName = $this->nodeNameResolver->getName($propertyFetch->var);
if (! is_string($varName)) {
throw new NotImplementedException();
}

$propertyName = $this->nodeNameResolver->getName($propertyFetch->name);
if (! is_string($propertyName)) {
throw new NotImplementedException();
}

return $varName . ucfirst($propertyName);
}

private function resolveParamNameFromMethodCall(MethodCall $methodCall): string
{
$varName = $this->nodeNameResolver->getName($methodCall->var);
if (! is_string($varName)) {
throw new NotImplementedException();
}

$methodName = $this->nodeNameResolver->getName($methodCall->name);
if (! is_string($methodName)) {
throw new NotImplementedException();
}

return $varName . ucfirst($methodName);
}
}
148 changes: 74 additions & 74 deletions rules/nette-kdyby/src/NodeFactory/CustomEventFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@
namespace Rector\NetteKdyby\NodeFactory;

use Nette\Utils\Strings;
use PhpParser\Builder\Class_;
use PhpParser\Builder\Class_ as ClassBuilder;
use PhpParser\Builder\Method;
use PhpParser\Builder\Namespace_ as NamespaceBuilder;
use PhpParser\Builder\Property as PropertyBuilder;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use Rector\CodingStyle\Naming\ClassNaming;
use Rector\Core\Exception\NotImplementedException;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\NetteKdyby\BlueprintFactory\VariableWithTypesFactory;
use Rector\NetteKdyby\ValueObject\VariableWithType;

/**
* @todo decouple to generic object factory for better re-use, e.g. this is just value object pattern
*/
final class CustomEventFactory
{
/**
Expand All @@ -32,117 +33,116 @@ final class CustomEventFactory
private $classNaming;

/**
* @var NodeNameResolver
* @var VariableWithTypesFactory
*/
private $nodeNameResolver;
private $variableWithTypesFactory;

public function __construct(ClassNaming $classNaming, NodeNameResolver $nodeNameResolver)
{
/**
* @var NodeFactory
*/
private $nodeFactory;

public function __construct(
ClassNaming $classNaming,
VariableWithTypesFactory $variableWithTypesFactory,
NodeFactory $nodeFactory
) {
$this->classNaming = $classNaming;
$this->nodeNameResolver = $nodeNameResolver;
$this->variableWithTypesFactory = $variableWithTypesFactory;
$this->nodeFactory = $nodeFactory;
}

/**
* @param Arg[] $args
*/
public function create(string $className, array $args): Namespace_
{
$namespace = Strings::before($className, '\\', -1);
$namespaceBuilder = new NamespaceBuilder($namespace);

$shortClassName = $this->classNaming->getShortName($className);
$classBuilder = new Class_($shortClassName);
$classBuilder->makeFinal();
$classBuilder->extend(new FullyQualified('Symfony\Contracts\EventDispatcher\Event'));
$classBuilder = $this->createEventClassBuilder($className);

// 1. add __construct if args?
// 2. add getters
// 3. add property

if (count($args) > 0) {
$methodBuilder = $this->createConstructClassMethod($args);
$classBuilder->addStmt($methodBuilder);

// add properties
foreach ($args as $arg) {
$property = $this->createProperty($arg);
$classBuilder->addStmt($property);
}

// add getters
foreach ($args as $arg) {
$getterClassMethod = $this->createGetterClassMethod($arg);
$classBuilder->addStmt($getterClassMethod);
}
}
$this->decorateWithConstructorIfHasArgs($classBuilder, $args);

$class = $classBuilder->getNode();
$namespaceBuilder->addStmt($class);

return $namespaceBuilder->getNode();
return $this->wrapClassToNamespace($className, $class);
}

/**
* @param Arg[] $args
* @param VariableWithType[] $variableWithTypes
*/
private function createConstructClassMethod(array $args): ClassMethod
private function createConstructClassMethod(array $variableWithTypes): ClassMethod
{
$methodBuilder = new Method('__construct');
$methodBuilder->makePublic();

foreach ($args as $arg) {
$paramName = $this->resolveParamNameFromArg($arg);
if ($paramName === null) {
throw new NotImplementedException();
foreach ($variableWithTypes as $variableWithType) {
$param = new Param(new Variable($variableWithType->getName()));

if ($variableWithType->getPhpParserTypeNode() !== null) {
$param->type = $variableWithType->getPhpParserTypeNode();
}

$param = new Param(new Variable($paramName));
$methodBuilder->addParam($param);

$assign = new Assign(new PropertyFetch(new Variable('this'), $paramName), new Variable($paramName));
$assign = new Assign(new PropertyFetch(new Variable('this'), $variableWithType->getName()), new Variable(
$variableWithType->getName()
));
$methodBuilder->addStmt($assign);
}

return $methodBuilder->getNode();
}

private function createProperty(Arg $arg): Property
private function createEventClassBuilder(string $className): ClassBuilder
{
$paramName = $this->resolveParamNameFromArg($arg);
if ($paramName === null) {
// @todo
throw new NotImplementedException();
}
$shortClassName = $this->classNaming->getShortName($className);

$classBuilder = new ClassBuilder($shortClassName);
$classBuilder->makeFinal();
$classBuilder->extend(new FullyQualified('Symfony\Contracts\EventDispatcher\Event'));

$propertyBuilder = new PropertyBuilder($paramName);
$propertyBuilder->makePrivate();
return $classBuilder;
}

private function wrapClassToNamespace(string $className, Class_ $class): Namespace_
{
$namespace = Strings::before($className, '\\', -1);
$namespaceBuilder = new NamespaceBuilder($namespace);
$namespaceBuilder->addStmt($class);

return $propertyBuilder->getNode();
return $namespaceBuilder->getNode();
}

private function createGetterClassMethod(Arg $arg): ClassMethod
/**
* @param Arg[] $args
*/
private function decorateWithConstructorIfHasArgs(ClassBuilder $classBuilder, array $args): void
{
$paramName = $this->resolveParamNameFromArg($arg);
if ($paramName === null) {
throw new NotImplementedException();
if (count($args) === 0) {
return;
}

$methodBuilder = new Method($paramName);
$variablesWithTypes = $this->variableWithTypesFactory->createVariablesWithTypesFromArgs($args);

$return = new Return_(new PropertyFetch(new Variable('this'), $paramName));
$methodBuilder->addStmt($return);
$methodBuilder->makePublic();
$methodBuilder = $this->createConstructClassMethod($variablesWithTypes);
$classBuilder->addStmt($methodBuilder);

return $methodBuilder->getNode();
}
// add properties
foreach ($variablesWithTypes as $variableWithType) {
$property = $this->nodeFactory->createPrivatePropertyFromNameAndType(
$variableWithType->getName(),
$variableWithType->getType()
);

private function resolveParamNameFromArg(Arg $arg): ?string
{
$argValue = $arg->value;
while ($argValue instanceof ArrayDimFetch) {
$argValue = $argValue->var;
$classBuilder->addStmt($property);
}

return $this->nodeNameResolver->getName($argValue);
// add getters
foreach ($variablesWithTypes as $variableWithType) {
$getterClassMethod = $this->nodeFactory->createGetterClassMethodFromNameAndType(
$variableWithType->getName(),
$variableWithType->getPhpParserTypeNode()
);
$classBuilder->addStmt($getterClassMethod);
}
}
}
Loading