diff --git a/composer.json b/composer.json index e8ebfc45..9b74bc36 100644 --- a/composer.json +++ b/composer.json @@ -16,14 +16,13 @@ "symfony/console": "^7.1" }, "require-dev": { - "rector/rector": "dev-main", - "nikic/php-parser": "^5.3.1", - "phpunit/phpunit": "^10.5", - "phpstan/phpstan": "^2.0", - "symplify/easy-coding-standard": "^12.1", + "rector/rector": "^2.0", + "nikic/php-parser": "^5.4", + "phpunit/phpunit": "^11.5", + "phpstan/phpstan": "^2.1", + "phpecs/phpecs": "^2.0", "phpstan/extension-installer": "^1.3", - "symplify/rule-doc-generator": "^12.1", - "tomasvotruba/class-leak": "^0.2.5", + "tomasvotruba/class-leak": "^2.0", "tracy/tracy": "^2.10" }, "autoload": { @@ -47,7 +46,7 @@ "phpstan": "vendor/bin/phpstan analyse --ansi", "check-cs": "vendor/bin/ecs check --ansi", "fix-cs": "vendor/bin/ecs check --fix --ansi", - "docs": "vendor/bin/rule-doc-generator generate src rules --output-file docs/rector_rules_overview.md --ansi" + "phpunit": "vendor/bin/phpunit --colors=always" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/rector.php b/rector.php index 07580e33..e4948dd2 100644 --- a/rector.php +++ b/rector.php @@ -4,26 +4,18 @@ use Rector\Config\RectorConfig; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; -use Rector\Set\ValueObject\SetList; - -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->importNames(); - - $rectorConfig->paths([__DIR__ . '/config', __DIR__ . '/src', __DIR__ . '/rules', __DIR__ . '/rules-tests']); - - $rectorConfig->skip([ - // for tests - '*/Source/*', +return RectorConfig::configure() + ->withImportNames() + ->withPaths([__DIR__ . '/config', __DIR__ . '/src', __DIR__ . '/rules', __DIR__ . '/rules-tests']) + ->withSkip([ StringClassNameToClassConstantRector::class => [__DIR__ . '/src/DocFactory.php'], - ]); - - $rectorConfig->sets([ - SetList::INSTANCEOF, - SetList::NAMING, - SetList::TYPE_DECLARATION, - SetList::DEAD_CODE, - SetList::CODE_QUALITY, - SetList::CODING_STYLE, - ]); -}; + ]) + ->withPreparedSets( + instanceOf: true, + naming: true, + typeDeclarations: true, + deadCode: true, + codeQuality: true, + codingStyle: true, + ); diff --git a/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/phpspec_promises.php.inc b/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/phpspec_promises.php.inc index 88dfc9ac..a4c4088d 100644 --- a/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/phpspec_promises.php.inc +++ b/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/phpspec_promises.php.inc @@ -22,6 +22,11 @@ use PhpSpec\ObjectBehavior; final class PhpSpecPromises extends ObjectBehavior { + private \Rector\PhpSpecToPHPUnit\Tests\Rector\Class_\PromisesToAssertsRector\Fixture\PhpSpecPromises $phpSpecPromises; + protected function setUp(): void + { + $this->phpSpecPromises = new \Rector\PhpSpecToPHPUnit\Tests\Rector\Class_\PromisesToAssertsRector\Fixture\PhpSpecPromises(); + } public function it_returns_id() { $this->assertSame(5, $this->phpSpecPromises->id()); diff --git a/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/should_be_instance_of.php.inc b/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/should_be_instance_of.php.inc index ea7d9377..a45972d1 100644 --- a/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/should_be_instance_of.php.inc +++ b/rules-tests/Rector/Class_/PromisesToAssertsRector/Fixture/should_be_instance_of.php.inc @@ -4,7 +4,7 @@ namespace spec\Rector\PhpSpecToPHPUnit; use PhpSpec\ObjectBehavior; -class TestClassMethod extends ObjectBehavior +class ShouldBeInstanceofMethod extends ObjectBehavior { public function let_1() { @@ -35,26 +35,31 @@ namespace spec\Rector\PhpSpecToPHPUnit; use PhpSpec\ObjectBehavior; -class TestClassMethod extends ObjectBehavior +class ShouldBeInstanceofMethod extends ObjectBehavior { + private \Rector\PhpSpecToPHPUnit\ShouldBeInstanceofMethod $shouldBeInstanceofMethod; + protected function setUp(): void + { + $this->shouldBeInstanceofMethod = new \Rector\PhpSpecToPHPUnit\ShouldBeInstanceofMethod(); + } public function let_1() { - $this->assertInstanceOf(\stdClass::class, $this->testClassMethod); + $this->assertInstanceOf(\stdClass::class, $this->shouldBeInstanceofMethod); } public function let_2() { - $this->assertInstanceOf(\stdClass::class, $this->testClassMethod); + $this->assertInstanceOf(\stdClass::class, $this->shouldBeInstanceofMethod); } public function let_3() { - $this->assertInstanceOf(\stdClass::class, $this->testClassMethod); + $this->assertInstanceOf(\stdClass::class, $this->shouldBeInstanceofMethod); } public function let_4() { - $this->assertInstanceOf(\stdClass::class, $this->testClassMethod); + $this->assertInstanceOf(\stdClass::class, $this->shouldBeInstanceofMethod); } } diff --git a/rules/Rector/Class_/ImplicitLetInitializationRector.php b/rules/Rector/Class_/ImplicitLetInitializationRector.php index 50076f0c..bed1935e 100644 --- a/rules/Rector/Class_/ImplicitLetInitializationRector.php +++ b/rules/Rector/Class_/ImplicitLetInitializationRector.php @@ -4,26 +4,18 @@ namespace Rector\PhpSpecToPHPUnit\Rector\Class_; -use PhpParser\Modifiers; use PhpParser\Node; -use PhpParser\Node\Expr\Assign; -use PhpParser\Node\Expr\New_; -use PhpParser\Node\Expr\PropertyFetch; -use PhpParser\Node\Expr\Variable; -use PhpParser\Node\Identifier; -use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\PhpSpecToPHPUnit\Enum\PhpSpecMethodName; use Rector\PhpSpecToPHPUnit\Naming\PhpSpecRenaming; +use Rector\PhpSpecToPHPUnit\NodeFactory\SetUpInstanceFactory; use Rector\PhpSpecToPHPUnit\NodeFinder\MethodCallFinder; use Rector\PhpSpecToPHPUnit\ValueObject\TestedObject; use Rector\Rector\AbstractRector; -use Rector\ValueObject\MethodName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -34,6 +26,7 @@ final class ImplicitLetInitializationRector extends AbstractRector { public function __construct( private readonly PhpSpecRenaming $phpSpecRenaming, + private readonly SetUpInstanceFactory $setUpInstanceFactory, ) { } @@ -108,26 +101,12 @@ public function refactor(Node $node): ?Node $testedObjectProperty = $this->createTestedObjectProperty($testedObject); - $setUpClassMethod = $this->createSetUpClassMethod($testedObject); + $setUpClassMethod = $this->setUpInstanceFactory->createSetUpClassMethod($testedObject); $node->stmts = [$testedObjectProperty, $setUpClassMethod, ...(array) $node->stmts]; return $node; } - private function createSetUpClassMethod(TestedObject $testedObject): ClassMethod - { - $classMethod = new ClassMethod(MethodName::SET_UP); - $classMethod->returnType = new Identifier('void'); - $classMethod->flags |= Modifiers::PROTECTED; - - $propertyFetch = new PropertyFetch(new Variable('this'), $testedObject->getPropertyName()); - $new = new New_(new FullyQualified($testedObject->getClassName())); - - $classMethod->stmts = [new Expression(new Assign($propertyFetch, $new))]; - - return $classMethod; - } - private function createTestedObjectProperty(TestedObject $testedObject): Property { return $this->nodeFactory->createPrivatePropertyFromNameAndType( diff --git a/rules/Rector/Class_/PromisesToAssertsRector.php b/rules/Rector/Class_/PromisesToAssertsRector.php index 0eda36f8..ceb701cc 100644 --- a/rules/Rector/Class_/PromisesToAssertsRector.php +++ b/rules/Rector/Class_/PromisesToAssertsRector.php @@ -4,6 +4,8 @@ namespace Rector\PhpSpecToPHPUnit\Rector\Class_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Identifier; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; @@ -12,7 +14,8 @@ use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\Class_; -use PHPStan\Node\ClassMethod; +use PhpParser\Node\Stmt\Property; +use PhpParser\NodeFinder; use Rector\PhpSpecToPHPUnit\Enum\PhpSpecMethodName; use Rector\PhpSpecToPHPUnit\Enum\PHPUnitMethodName; use Rector\PhpSpecToPHPUnit\Enum\ProphecyPromisesToPHPUnitAssertMap; @@ -20,7 +23,10 @@ use Rector\PhpSpecToPHPUnit\Naming\SystemMethodDetector; use Rector\PhpSpecToPHPUnit\NodeFactory\AssertMethodCallFactory; use Rector\PhpSpecToPHPUnit\NodeFactory\BeConstructedWithAssignFactory; +use Rector\PhpSpecToPHPUnit\NodeFactory\SetUpInstanceFactory; +use Rector\PhpSpecToPHPUnit\ValueObject\TestedObject; use Rector\Rector\AbstractRector; +use Rector\ValueObject\MethodName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -29,11 +35,15 @@ */ final class PromisesToAssertsRector extends AbstractRector { + private NodeFinder $nodeFinder; + public function __construct( private readonly PhpSpecRenaming $phpSpecRenaming, private readonly AssertMethodCallFactory $assertMethodCallFactory, private readonly BeConstructedWithAssignFactory $beConstructedWithAssignFactory, + private readonly SetUpInstanceFactory $setUpInstanceFactory, ) { + $this->nodeFinder = new NodeFinder(); } /** @@ -58,6 +68,8 @@ public function refactor(Node $node): Node|null $localMethodNames = $this->getLocalMethodNames($node); + $needsSetUp = true; + foreach ($node->getMethods() as $classMethod) { if (! $classMethod->isPublic()) { continue; @@ -68,9 +80,15 @@ public function refactor(Node $node): Node|null $classMethod, [PhpSpecMethodName::LET, PhpSpecMethodName::LET_GO, PhpSpecMethodName::GET_MATCHERS] )) { + $needsSetUp = false; continue; } + // if all methods have this call, no need to add setUp() + if ($this->hasBeConstructedMethodCall($classMethod)) { + $needsSetUp = false; + } + $this->traverseNodesWithCallable($classMethod, function (Node $node) use ( $class, $testedObjectPropertyFetch, @@ -179,6 +197,16 @@ public function refactor(Node $node): Node|null return null; } + // add setUp() method with the property + if ($needsSetUp && ! $class->getMethod(MethodName::SET_UP)) { + $testedObject = $this->phpSpecRenaming->resolveTestedObject($node); + $setUpClassMethod = $this->setUpInstanceFactory->createSetUpClassMethod($testedObject); + + $testedObjectProperty = $this->createTestedObjectProperty($testedObject); + + $class->stmts = array_merge([$testedObjectProperty, $setUpClassMethod], $class->stmts); + } + return $node; } @@ -248,4 +276,29 @@ private function getLocalMethodNames(Class_ $class): array /** @var string[] $localMethodNames */ return $localMethodNames; } + + private function createTestedObjectProperty(TestedObject $testedObject): Property + { + return $this->nodeFactory->createPrivatePropertyFromNameAndType( + $testedObject->getPropertyName(), + $testedObject->getTestedObjectType() + ); + } + + private function hasBeConstructedMethodCall(ClassMethod $classMethod): bool + { + $methodCall = $this->nodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { + if (! $node instanceof MethodCall) { + return false; + } + + if (! $node->name instanceof Identifier) { + return false; + } + + return $node->name->toString() === 'beConstructedWith'; + }); + + return $methodCall instanceof MethodCall; + } } diff --git a/src/NodeFactory/SetUpInstanceFactory.php b/src/NodeFactory/SetUpInstanceFactory.php new file mode 100644 index 00000000..5a8f0ddd --- /dev/null +++ b/src/NodeFactory/SetUpInstanceFactory.php @@ -0,0 +1,34 @@ +returnType = new Identifier('void'); + $classMethod->flags |= Modifiers::PROTECTED; + + $propertyFetch = new PropertyFetch(new Variable('this'), $testedObject->getPropertyName()); + $new = new New_(new FullyQualified($testedObject->getClassName())); + + $classMethod->stmts = [new Expression(new Assign($propertyFetch, $new))]; + + return $classMethod; + } +} diff --git a/src/NodeFactory/WillReturnMapMethodCallFactory.php b/src/NodeFactory/WillReturnMapMethodCallFactory.php index 64b084a6..3aee12bb 100644 --- a/src/NodeFactory/WillReturnMapMethodCallFactory.php +++ b/src/NodeFactory/WillReturnMapMethodCallFactory.php @@ -93,7 +93,7 @@ private function createConsecutiveItemsArray( PhpSpecMethodName::SHOULD_RETURN ); - if (empty($returnArgs)) { + if ($returnArgs === []) { $returnArgs = $this->resolveInputArgs( $consecutiveMethodCall->getMethodCall(), PhpSpecMethodName::WILL_RETURN diff --git a/tests/Sets/Fixture/Sylius/add_setup.php.inc b/tests/Sets/Fixture/Sylius/add_setup.php.inc new file mode 100644 index 00000000..21a748e1 --- /dev/null +++ b/tests/Sets/Fixture/Sylius/add_setup.php.inc @@ -0,0 +1,41 @@ +shouldHaveType(SomeTestedClass::class); + } + + public function it_implements(): void + { + $this->shouldImplement(RandomInterface::class); + } +} + +?> +----- +someTestedClass = new \Set\Sylius\SomeTestedClass(); + } + public function testImplements(): void + { + $this->assertInstanceOf(RandomInterface::class, $this->someTestedClass); + } +} + +?> diff --git a/tests/Sets/Fixture/is_array_type.php.inc b/tests/Sets/Fixture/is_array_type.php.inc index e692dfa0..78921e5e 100644 --- a/tests/Sets/Fixture/is_array_type.php.inc +++ b/tests/Sets/Fixture/is_array_type.php.inc @@ -26,6 +26,11 @@ use Sets\Source\Cart; final class IsArrayTypeTest extends \PHPUnit\Framework\TestCase { + private \Rector\PhpSpecToPHPUnit\Tests\Sets\Fixture\IsArrayType $isArrayType; + protected function setUp(): void + { + $this->isArrayType = new \Rector\PhpSpecToPHPUnit\Tests\Sets\Fixture\IsArrayType(); + } public function testArrayType(): void { $this->assertIsIterable($this->isArrayType->shippingAddresses()); diff --git a/tests/Sets/Fixture/property_fetch_call.php.inc b/tests/Sets/Fixture/property_fetch_call.php.inc index b42bc654..10fa430f 100644 --- a/tests/Sets/Fixture/property_fetch_call.php.inc +++ b/tests/Sets/Fixture/property_fetch_call.php.inc @@ -24,6 +24,11 @@ use Sets\Source\Address; final class PropertyFetchCallTest extends \PHPUnit\Framework\TestCase { + private \Rector\PhpSpecToPHPUnit\Tests\Sets\Fixture\PropertyFetchCall $propertyFetchCall; + protected function setUp(): void + { + $this->propertyFetchCall = new \Rector\PhpSpecToPHPUnit\Tests\Sets\Fixture\PropertyFetchCall(); + } public function testThrowsAnExceptionIfTheCardIsNotInTheUsersWallet(): void { /** @var \Sets\Source\Address|\PHPUnit\Framework\MockObject\MockObject $addressMock */ diff --git a/tests/Sets/Fixture/should_return.php.inc b/tests/Sets/Fixture/should_return.php.inc index 9bd18554..b4fb75c6 100644 --- a/tests/Sets/Fixture/should_return.php.inc +++ b/tests/Sets/Fixture/should_return.php.inc @@ -26,6 +26,11 @@ use Sets\Source\Cart; final class ShouldReturnTest extends \PHPUnit\Framework\TestCase { + private \PhpSpecToPHPUnit\Fixture\ShouldReturn $shouldReturn; + protected function setUp(): void + { + $this->shouldReturn = new \PhpSpecToPHPUnit\Fixture\ShouldReturn(); + } public function testReturnsId(): void { $this->assertSame(5, $this->shouldReturn->id()); diff --git a/tests/Sets/Fixture/should_return_value.php.inc b/tests/Sets/Fixture/should_return_value.php.inc index cc05f45d..eafac39f 100644 --- a/tests/Sets/Fixture/should_return_value.php.inc +++ b/tests/Sets/Fixture/should_return_value.php.inc @@ -4,7 +4,7 @@ namespace Sets\Fixture; use PhpSpec\ObjectBehavior; -class ShouldReturnSpec extends ObjectBehavior +class ShouldReturnValueSpec extends ObjectBehavior { public function let() { @@ -30,17 +30,17 @@ namespace Sets\Fixture; use PhpSpec\ObjectBehavior; -final class ShouldReturnTest extends \PHPUnit\Framework\TestCase +final class ShouldReturnValueTest extends \PHPUnit\Framework\TestCase { - private \Sets\Fixture\ShouldReturn $shouldReturn; + private \Sets\Fixture\ShouldReturnValue $shouldReturnValue; protected function setUp(): void { - $this->shouldReturn = new \Sets\Fixture\ShouldReturn('some'); + $this->shouldReturnValue = new \Sets\Fixture\ShouldReturnValue('some'); } public function testCalculateZeroPriceWhenCartIsEmpty(): void { - $price = $this->shouldReturn->price(); + $price = $this->shouldReturnValue->price(); $this->assertInstanceOf('someType', $price); $this->assertSame(0.0, $price->withVat());