[DX] Remove MoveValueObjectsToValueObjectDirectoryRector, should be handled by PHPStorm refactoring and PHPStan rule checks (#1832)

This commit is contained in:
Tomas Votruba 2022-02-18 01:00:33 +00:00 committed by GitHub
parent cab8299093
commit 1ed8242e44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2 additions and 571 deletions

View File

@ -1,4 +1,4 @@
# 518 Rules Overview
# 517 Rules Overview
<br>
@ -6,7 +6,7 @@
- [Arguments](#arguments) (4)
- [Autodiscovery](#autodiscovery) (4)
- [Autodiscovery](#autodiscovery) (3)
- [CodeQuality](#codequality) (71)
@ -326,53 +326,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
<br>
### MoveValueObjectsToValueObjectDirectoryRector
Move value object to ValueObject namespace/directory
:wrench: **configure it!**
- class: [`Rector\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector`](../rules/Autodiscovery/Rector/Class_/MoveValueObjectsToValueObjectDirectoryRector.php)
```php
use Rector\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(MoveValueObjectsToValueObjectDirectoryRector::class)
->configure([
MoveValueObjectsToValueObjectDirectoryRector::TYPES => ['ValueObjectInterfaceClassName'],
MoveValueObjectsToValueObjectDirectoryRector::SUFFIXES => ['Search'],
MoveValueObjectsToValueObjectDirectoryRector::ENABLE_VALUE_OBJECT_GUESSING => true,
]);
};
```
```diff
-// app/Exception/Name.php
+// app/ValueObject/Name.php
class Name
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
```
<br>
## CodeQuality
### AbsolutizeRequireAndIncludePathRector

View File

@ -1,8 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\ValueObject;
final class MeSearch
{
}

View File

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\ValueObject;
class PrimitiveValueObject
{
/**
* @var string
*/
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}

View File

@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\ValueObject;
use Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\ObviousValueObjectInterface;
final class SomeName implements \Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\ObviousValueObjectInterface
{
}

View File

@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector;
use Iterator;
use Rector\FileSystemRector\ValueObject\AddedFileWithContent;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
final class MoveValueObjectsToValueObjectDirectoryRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fixtureFileInfo, ?AddedFileWithContent $expectedAddedFileWithContent): void
{
$this->doTestFileInfo($fixtureFileInfo);
if ($expectedAddedFileWithContent !== null) {
$this->assertFileWasAdded($expectedAddedFileWithContent);
} else {
$this->assertFileWasNotChanged($this->originalTempFileInfo);
}
}
/**
* @return Iterator<mixed>
*/
public function provideData(): Iterator
{
$smartFileSystem = new SmartFileSystem();
yield [
new SmartFileInfo(__DIR__ . '/Source/Repository/PrimitiveValueObject.php'),
new AddedFileWithContent(
$this->getFixtureTempDirectory() . '/ValueObject/PrimitiveValueObject.php',
$smartFileSystem->readFile(__DIR__ . '/Expected/ValueObject/PrimitiveValueObject.php')
),
];
// type
yield [
new SmartFileInfo(__DIR__ . '/Source/Command/SomeName.php'),
new AddedFileWithContent(
$this->getFixtureTempDirectory() . '/ValueObject/SomeName.php',
$smartFileSystem->readFile(__DIR__ . '/Expected/ValueObject/SomeName.php')
),
];
// suffix
yield [
new SmartFileInfo(__DIR__ . '/Source/Command/MeSearch.php'),
new AddedFileWithContent(
$this->getFixtureTempDirectory() . '/ValueObject/MeSearch.php',
$smartFileSystem->readFile(__DIR__ . '/Expected/ValueObject/MeSearch.php')
),
];
// skip known service types
yield [new SmartFileInfo(__DIR__ . '/Source/Utils/SomeSuffixedTest.php.inc'), null];
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\Command;
final class MeSearch
{
}

View File

@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\Command;
use Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\ObviousValueObjectInterface;
final class SomeName implements ObviousValueObjectInterface
{
}

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source;
interface ObviousValueObjectInterface
{
}

View File

@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\Repository;
class PrimitiveValueObject
{
/**
* @var string
*/
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\Utils;
final class SomeSuffixedTest
{
}

View File

@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
use Rector\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector;
use Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\Source\ObviousValueObjectInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(MoveValueObjectsToValueObjectDirectoryRector::class)
->configure([
MoveValueObjectsToValueObjectDirectoryRector::TYPES => [ObviousValueObjectInterface::class],
MoveValueObjectsToValueObjectDirectoryRector::SUFFIXES => ['Search'],
MoveValueObjectsToValueObjectDirectoryRector::ENABLE_VALUE_OBJECT_GUESSING => true,
]);
};

View File

@ -1,104 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Autodiscovery\Analyzer;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Type\ObjectType;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Core\NodeAnalyzer\ClassAnalyzer;
use Rector\Core\PhpParser\AstResolver;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class ValueObjectClassAnalyzer
{
/**
* @var array<string, bool>
*/
private array $valueObjectStatusByClassName = [];
public function __construct(
private readonly NodeTypeResolver $nodeTypeResolver,
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly AstResolver $astResolver,
private readonly ClassAnalyzer $classAnalyzer,
private readonly NodeNameResolver $nodeNameResolver
) {
}
public function isValueObjectClass(Class_ $class): bool
{
if ($this->classAnalyzer->isAnonymousClass($class)) {
return false;
}
/** @var string $className */
$className = (string) $this->nodeNameResolver->getName($class);
if (isset($this->valueObjectStatusByClassName[$className])) {
return $this->valueObjectStatusByClassName[$className];
}
$constructClassMethod = $class->getMethod(MethodName::CONSTRUCT);
if (! $constructClassMethod instanceof ClassMethod) {
return $this->hasExlusivelySerializeProperties($class, $className);
}
// resolve constructor types
foreach ($constructClassMethod->params as $param) {
$paramType = $this->nodeTypeResolver->getType($param);
if (! $paramType instanceof ObjectType) {
continue;
}
// awesome!
// is it services or value object?
$paramTypeClass = $this->astResolver->resolveClassFromName($paramType->getClassName());
if (! $paramTypeClass instanceof Class_) {
// not sure :/
continue;
}
if (! $this->isValueObjectClass($paramTypeClass)) {
return false;
}
}
// if we didn't prove it's not a value object so far → fallback to true
$this->valueObjectStatusByClassName[$className] = true;
return true;
}
private function hasExlusivelySerializeProperties(Class_ $class, string $className): bool
{
// A. has all properties with serialize?
if ($this->hasAllPropertiesWithSerialize($class)) {
$this->valueObjectStatusByClassName[$className] = true;
return true;
}
// probably not a value object
$this->valueObjectStatusByClassName[$className] = false;
return false;
}
private function hasAllPropertiesWithSerialize(Class_ $class): bool
{
foreach ($class->getProperties() as $property) {
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property);
if ($phpDocInfo->hasByAnnotationClass('JMS\Serializer\Annotation\Type')) {
continue;
}
return false;
}
return true;
}
}

View File

@ -1,230 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Autodiscovery\Rector\Class_;
use Controller;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use Rector\Autodiscovery\Analyzer\ValueObjectClassAnalyzer;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\FileSystemRector\ValueObject\AddedFileWithNodes;
use Rector\FileSystemRector\ValueObjectFactory\AddedFileWithNodesFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;
/**
* Inspiration @see https://github.com/rectorphp/rector/pull/1865/files#diff-0d18e660cdb626958662641b491623f8
* @wip
*
* @see \Rector\Tests\Autodiscovery\Rector\Class_\MoveValueObjectsToValueObjectDirectoryRector\MoveValueObjectsToValueObjectDirectoryRectorTest
*/
final class MoveValueObjectsToValueObjectDirectoryRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var string
*/
public const TYPES = 'types';
/**
* @var string
*/
public const SUFFIXES = 'suffixes';
/**
* @api
* @var string
*/
public const ENABLE_VALUE_OBJECT_GUESSING = 'enable_value_object_guessing';
/**
* @var string[]|class-string<Controller>[]
*/
private const COMMON_SERVICE_SUFFIXES = [
'Repository', 'Command', 'Mapper', 'Controller', 'Presenter', 'Factory', 'Test', 'TestCase', 'Service',
];
private bool $enableValueObjectGuessing = true;
/**
* @var string[]
*/
private array $types = [];
/**
* @var string[]
*/
private array $suffixes = [];
public function __construct(
private readonly AddedFileWithNodesFactory $addedFileWithNodesFactory,
private readonly ValueObjectClassAnalyzer $valueObjectClassAnalyzer
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Move value object to ValueObject namespace/directory', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
// app/Exception/Name.php
class Name
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
// app/ValueObject/Name.php
class Name
{
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
CODE_SAMPLE
,
[
self::TYPES => ['ValueObjectInterfaceClassName'],
self::SUFFIXES => ['Search'],
self::ENABLE_VALUE_OBJECT_GUESSING => true,
]
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isValueObjectMatch($node)) {
return null;
}
$smartFileInfo = $this->file->getSmartFileInfo();
$addedFileWithNodes = $this->addedFileWithNodesFactory->createWithDesiredGroup(
$smartFileInfo,
$this->file,
'ValueObject'
);
if (! $addedFileWithNodes instanceof AddedFileWithNodes) {
return null;
}
$this->removedAndAddedFilesCollector->removeFile($smartFileInfo);
$this->removedAndAddedFilesCollector->addAddedFile($addedFileWithNodes);
return null;
}
/**
* @param mixed[] $configuration
*/
public function configure(array $configuration): void
{
$types = $configuration[self::TYPES] ?? [];
Assert::isArray($types);
Assert::allString($types);
$this->types = $types;
$suffixes = $configuration[self::SUFFIXES] ?? [];
Assert::isArray($suffixes);
Assert::allString($suffixes);
$this->suffixes = $suffixes;
$enableValueObjectGuessing = $configuration[self::ENABLE_VALUE_OBJECT_GUESSING] ?? false;
Assert::boolean($enableValueObjectGuessing);
$this->enableValueObjectGuessing = $enableValueObjectGuessing;
}
private function isValueObjectMatch(Class_ $class): bool
{
if ($this->isSuffixMatch($class)) {
return true;
}
$className = $this->getName($class);
if ($className === null) {
return false;
}
$classObjectType = new ObjectType($className);
foreach ($this->types as $type) {
$desiredObjectType = new ObjectType($type);
if ($desiredObjectType->isSuperTypeOf($classObjectType)->yes()) {
return true;
}
}
if ($this->isKnownServiceType($className)) {
return false;
}
if (! $this->enableValueObjectGuessing) {
return false;
}
return $this->valueObjectClassAnalyzer->isValueObjectClass($class);
}
private function isSuffixMatch(Class_ $class): bool
{
$className = $this->getName($class);
if (! is_string($className)) {
return false;
}
foreach ($this->suffixes as $suffix) {
if (\str_ends_with($className, $suffix)) {
return true;
}
}
return false;
}
private function isKnownServiceType(string $className): bool
{
foreach (self::COMMON_SERVICE_SUFFIXES as $commonServiceSuffix) {
if (\str_ends_with($className, $commonServiceSuffix)) {
return true;
}
}
return false;
}
}