From 042e1c45e095eb675cc364878c06bdeaceef4351 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 11 Oct 2019 01:27:21 +0100 Subject: [PATCH] [PHPUnit 6] Add AddDoesNotPerformAssertionToNonAssertingTestRector --- config/set/phpunit/phpunit60.yaml | 1 + .../RemoveDefaultArgumentValueRector.php | 27 +- packages/NodeTypeResolver/config/config.yaml | 3 - .../PHPStanServicesFactory.php | 6 - ...rformAssertionToNonAssertingTestRector.php | 316 ++++++++++++++++++ .../RemoveDataProviderTestPrefixRector.php | 3 +- ...mAssertionToNonAssertingTestRectorTest.php | 39 +++ .../Fixture/fixture.php.inc | 30 ++ .../Fixture/keep_assert.php.inc | 18 + .../Fixture/keep_assert_in_call.php.inc | 16 + .../keep_assert_in_static_call.php.inc | 18 + .../Fixture/keep_expected_exception.php.inc | 21 ++ .../Fixture/keep_non_public.php.inc | 10 + .../Fixture/keep_non_test.php.inc | 10 + .../keep_with_parent_method_assert.php.inc | 13 + ...p_with_parent_method_static_assert.php.inc | 13 + .../Fixture/test_in_annotation.php.inc | 34 ++ .../Source/AbstractClassWithAssert.php | 18 + .../Source/AbstractClassWithStaticAssert.php | 18 + rector.yaml | 2 +- .../RemovedAndAddedFilesProcessor.php | 6 +- src/Application/RectorApplication.php | 3 +- src/Console/Command/ScreenFileCommand.php | 6 +- src/NodeContainer/ParsedNodesByType.php | 43 +++ src/Rector/AbstractPHPUnitRector.php | 20 ++ src/Rector/AbstractRector.php | 3 +- .../ClassMethodReflectionFactory.php | 52 +++ src/Reflection/FunctionReflectionResolver.php | 25 -- 28 files changed, 717 insertions(+), 57 deletions(-) create mode 100644 packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/AddDoesNotPerformAssertionToNonAssertingTestRectorTest.php create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/fixture.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_call.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_static_call.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_expected_exception.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_non_public.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_non_test.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_assert.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_static_assert.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/test_in_annotation.php.inc create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithAssert.php create mode 100644 packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithStaticAssert.php create mode 100644 src/Reflection/ClassMethodReflectionFactory.php delete mode 100644 src/Reflection/FunctionReflectionResolver.php diff --git a/config/set/phpunit/phpunit60.yaml b/config/set/phpunit/phpunit60.yaml index 56a8800a0296..6dfe1cdad9f6 100644 --- a/config/set/phpunit/phpunit60.yaml +++ b/config/set/phpunit/phpunit60.yaml @@ -9,3 +9,4 @@ services: PHPUnit_: # exclude this class, since it has no namespaced replacement - 'PHPUnit_Framework_MockObject_MockObject' + Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector: ~ diff --git a/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php b/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php index 154d5ff0d132..4611c5bc1bff 100644 --- a/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php +++ b/packages/DeadCode/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php @@ -16,7 +16,6 @@ use Rector\Rector\AbstractRector; use Rector\RectorDefinition\CodeSample; use Rector\RectorDefinition\RectorDefinition; -use Rector\Reflection\FunctionReflectionResolver; use ReflectionFunction; /** @@ -29,17 +28,9 @@ final class RemoveDefaultArgumentValueRector extends AbstractRector */ private $parsedNodesByType; - /** - * @var FunctionReflectionResolver - */ - private $functionReflectionResolver; - - public function __construct( - ParsedNodesByType $parsedNodesByType, - FunctionReflectionResolver $functionReflectionResolver - ) { + public function __construct(ParsedNodesByType $parsedNodesByType) + { $this->parsedNodesByType = $parsedNodesByType; - $this->functionReflectionResolver = $functionReflectionResolver; } public function getDefinition(): RectorDefinition @@ -245,12 +236,18 @@ private function shouldSkip(Node $node): bool return false; } - // skip native functions, hard to analyze without stubs (stubs would make working with IDE non-practical) - $functionName = $this->getName($node); - if (! is_string($functionName)) { + $functionName = $this->getName($node->name); + if ($functionName === null) { return false; } - return $this->functionReflectionResolver->isPhpNativeFunction($functionName); + if (! function_exists($functionName)) { + return false; + } + + $reflectionFunction = new ReflectionFunction($functionName); + + // skip native functions, hard to analyze without stubs (stubs would make working with IDE non-practical) + return $reflectionFunction->isInternal(); } } diff --git a/packages/NodeTypeResolver/config/config.yaml b/packages/NodeTypeResolver/config/config.yaml index b70a912189ae..6fd748c9caf8 100644 --- a/packages/NodeTypeResolver/config/config.yaml +++ b/packages/NodeTypeResolver/config/config.yaml @@ -25,6 +25,3 @@ services: PHPStan\Analyser\ScopeFactory: factory: ['@Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory', 'createScopeFactory'] - - PHPStan\Reflection\SignatureMap\SignatureMapProvider: - factory: ['@Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory', 'createSignatureMapProvider'] diff --git a/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php b/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php index 69d68ffad647..4448f8aebbbf 100644 --- a/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php +++ b/packages/NodeTypeResolver/src/DependencyInjection/PHPStanServicesFactory.php @@ -10,7 +10,6 @@ use PHPStan\Analyser\TypeSpecifier; use PHPStan\Broker\Broker; use PHPStan\DependencyInjection\ContainerFactory; -use PHPStan\Reflection\SignatureMap\SignatureMapProvider; final class PHPStanServicesFactory { @@ -50,11 +49,6 @@ public function createTypeSpecifier(): TypeSpecifier return $this->container->getByType(TypeSpecifier::class); } - public function createSignatureMapProvider(): SignatureMapProvider - { - return $this->container->getByType(SignatureMapProvider::class); - } - public function createScopeFactory(): ScopeFactory { return $this->container->getByType(ScopeFactory::class); diff --git a/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php b/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php new file mode 100644 index 000000000000..d8135237eb52 --- /dev/null +++ b/packages/PHPUnit/src/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector.php @@ -0,0 +1,316 @@ +docBlockManipulator = $docBlockManipulator; + $this->parsedNodesByType = $parsedNodesByType; + $this->fileInfoParser = $fileInfoParser; + $this->classMethodReflectionFactory = $classMethodReflectionFactory; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Tests without assertion will have @doesNotPerformAssertion ', [ + new CodeSample( + <<<'PHP' +class SomeClass extends PHPUnit\Framework\TestCase +{ + public function test() + { + $nothing = 5; + } +} +PHP + , + <<<'PHP' +class SomeClass extends PHPUnit\Framework\TestCase +{ + /** + * @doesNotPerformAssertion + */ + public function test() + { + $nothing = 5; + } +} +PHP + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkipClassMethod($node)) { + return null; + } + + $this->addDoesNotPerformAssertion($node); + + return $node; + } + + private function addDoesNotPerformAssertion(ClassMethod $classMethod): void + { + // A. create new doc + $doc = $classMethod->getDocComment(); + if ($doc === null) { + $text = sprintf('/**%s * @doesNotPerformAssertion%s */', PHP_EOL, PHP_EOL); + $classMethod->setDocComment(new Doc($text)); + return; + } + + // B. extend current doc + /** @var PhpDocInfo $phpDocInfo */ + $phpDocInfo = $this->getPhpDocInfo($classMethod); + $phpDocNode = $phpDocInfo->getPhpDocNode(); + $phpDocNode->children[] = new AttributeAwarePhpDocTagNode('@doesNotPerformAssertion', new GenericTagValueNode( + '' + )); + + $this->docBlockManipulator->updateNodeWithPhpDocInfo($classMethod, $phpDocInfo); + } + + private function shouldSkipClassMethod(ClassMethod $classMethod): bool + { + if (! $this->isInTestClass($classMethod)) { + return true; + } + + if (! $this->isTestClassMethod($classMethod)) { + return true; + } + + if ($classMethod->getDocComment()) { + $text = $classMethod->getDocComment(); + if (Strings::match($text->getText(), '#@expectedException\b#')) { + return true; + } + } + + if ($this->containsAssertCall($classMethod)) { + return true; + } + + return false; + } + + private function containsAssertCall(ClassMethod $classMethod): bool + { + $cacheHash = md5($this->print($classMethod)); + if (isset($this->containsAssertCallByClassMethod[$cacheHash])) { + return $this->containsAssertCallByClassMethod[$cacheHash]; + } + + // A. try "->assert" shallow search first for performance + $hasDirectAssertCall = (bool) $this->hasDirectAssertCall($classMethod); + if ($hasDirectAssertCall) { + $this->containsAssertCallByClassMethod[$cacheHash] = $hasDirectAssertCall; + return $hasDirectAssertCall; + } + + // B. look for nested calls + $hasNestedAssertCall = $this->hasNestedAssertCall($classMethod); + $this->containsAssertCallByClassMethod[$cacheHash] = $hasNestedAssertCall; + + return $hasNestedAssertCall; + } + + private function findClassMethodInFile(string $fileName, string $methodName): ?ClassMethod + { + // skip already anayzed method to prevent cycling + if (isset($this->analyzedMethodsInFileName[$fileName][$methodName])) { + return $this->analyzedMethodsInFileName[$fileName][$methodName]; + } + + $smartFileInfo = new SmartFileInfo($fileName); + $examinedMethodNodes = $this->fileInfoParser->parseFileInfoToNodesAndDecorate($smartFileInfo); + + /** @var ClassMethod|null $examinedClassMethod */ + $examinedClassMethod = $this->betterNodeFinder->findFirst( + $examinedMethodNodes, + function (Node $node) use ($methodName): bool { + if (! $node instanceof ClassMethod) { + return false; + } + + return $this->isName($node, $methodName); + } + ); + + $this->analyzedMethodsInFileName[$fileName][$methodName] = $examinedClassMethod; + + return $examinedClassMethod; + } + + /** + * @param MethodCall|StaticCall $node + */ + private function findClassMethod(Node $node): ?ClassMethod + { + if ($node instanceof MethodCall) { + $classMethod = $this->parsedNodesByType->findClassMethodByMethodCall($node); + if ($classMethod) { + return $classMethod; + } + } elseif ($node instanceof StaticCall) { + $classMethod = $this->parsedNodesByType->findClassMethodByStaticCall($node); + if ($classMethod) { + return $classMethod; + } + } + + // in 3rd-party code + return $this->findClassMethodByParsingReflection($node); + } + + /** + * @param MethodCall|StaticCall $node + */ + private function findClassMethodByParsingReflection(Node $node): ?ClassMethod + { + $methodName = $this->getName($node->name); + if ($methodName === null) { + return null; + } + + if ($node instanceof MethodCall) { + $objectType = $this->getObjectType($node->var); + } else { + // StaticCall + $objectType = $this->getObjectType($node->class); + } + + $reflectionMethod = $this->classMethodReflectionFactory->createFromPHPStanTypeAndMethodName( + $objectType, + $methodName + ); + + if ($reflectionMethod === null) { + return null; + } + + $fileName = $reflectionMethod->getFileName(); + if ($fileName === false || ! file_exists($fileName)) { + return null; + } + + return $this->findClassMethodInFile($fileName, $methodName); + } + + private function hasDirectAssertCall(ClassMethod $classMethod): bool + { + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node): bool { + if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { + return false; + } + + if ($this->isName($node->name, 'assert*')) { + return true; + } + + // expectException(...) + if ($this->isName($node->name, 'expectException*')) { + return true; + } + + return false; + }); + } + + private function hasNestedAssertCall(ClassMethod $classMethod): bool + { + $currentClassMethod = $classMethod; + + // over and over the same method :/ + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use ( + $currentClassMethod + ): bool { + if (! $node instanceof MethodCall && ! $node instanceof StaticCall) { + return false; + } + + $classMethod = $this->findClassMethod($node); + + // skip circular self calls + if ($currentClassMethod === $classMethod) { + return false; + } + + if ($classMethod) { + return $this->containsAssertCall($classMethod); + } + + return false; + }); + } +} diff --git a/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php b/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php index 3470cd0bee0b..641ea899933a 100644 --- a/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php +++ b/packages/PHPUnit/src/Rector/Class_/RemoveDataProviderTestPrefixRector.php @@ -7,6 +7,7 @@ use Nette\Utils\Strings; use PhpParser\Comment\Doc; use PhpParser\Node; +use PhpParser\Node\Identifier; use PhpParser\Node\Stmt\Class_; use Rector\Rector\AbstractPHPUnitRector; use Rector\RectorDefinition\CodeSample; @@ -142,7 +143,7 @@ private function renameProviderMethods(Class_ $class): void continue; } - $classMethod->name = new Node\Identifier($newName); + $classMethod->name = new Identifier($newName); } } } diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/AddDoesNotPerformAssertionToNonAssertingTestRectorTest.php b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/AddDoesNotPerformAssertionToNonAssertingTestRectorTest.php new file mode 100644 index 000000000000..834e4d40d037 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/AddDoesNotPerformAssertionToNonAssertingTestRectorTest.php @@ -0,0 +1,39 @@ +doTestFile($file); + } + + public function provideDataForTest(): Iterator + { + yield [__DIR__ . '/Fixture/fixture.php.inc']; + yield [__DIR__ . '/Fixture/test_in_annotation.php.inc']; + yield [__DIR__ . '/Fixture/keep_assert.php.inc']; + yield [__DIR__ . '/Fixture/keep_expected_exception.php.inc']; + yield [__DIR__ . '/Fixture/keep_assert_in_call.php.inc']; + yield [__DIR__ . '/Fixture/keep_assert_in_static_call.php.inc']; + yield [__DIR__ . '/Fixture/keep_non_public.php.inc']; + yield [__DIR__ . '/Fixture/keep_non_test.php.inc']; + yield [__DIR__ . '/Fixture/keep_with_parent_method_assert.php.inc']; + yield [__DIR__ . '/Fixture/keep_with_parent_method_static_assert.php.inc']; + } + + protected function getRectorClass(): string + { + return AddDoesNotPerformAssertionToNonAssertingTestRector::class; + } +} diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/fixture.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..eb62dfe2cdae --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/fixture.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert.php.inc new file mode 100644 index 000000000000..19df5f1d6e5e --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert.php.inc @@ -0,0 +1,18 @@ +assertNotNull(5); + } + + public function testStatic() + { + $nothing = 5; + self::assertNotNull(5); + } +} diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_call.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_call.php.inc new file mode 100644 index 000000000000..bccb46894e19 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_call.php.inc @@ -0,0 +1,16 @@ +doElsewhere(5); + } + + private function doElsewhere($value) + { + $this->assertNotNull($value); + } +} diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_static_call.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_static_call.php.inc new file mode 100644 index 000000000000..eeca0c0bc0ce --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_assert_in_static_call.php.inc @@ -0,0 +1,18 @@ +expectException('Throwable'); + throw new InvalidArgumentException(); + } +} diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_non_public.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_non_public.php.inc new file mode 100644 index 000000000000..01a4bd7aee43 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_non_public.php.inc @@ -0,0 +1,10 @@ +doAssertThis(); + } +} diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_static_assert.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_static_assert.php.inc new file mode 100644 index 000000000000..f4d5c8f1620c --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/keep_with_parent_method_static_assert.php.inc @@ -0,0 +1,13 @@ +doAssertThis(); + } +} diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/test_in_annotation.php.inc b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/test_in_annotation.php.inc new file mode 100644 index 000000000000..bc89ca8ba967 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Fixture/test_in_annotation.php.inc @@ -0,0 +1,34 @@ + +----- + diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithAssert.php b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithAssert.php new file mode 100644 index 000000000000..89a16e5de241 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithAssert.php @@ -0,0 +1,18 @@ +anotherMethod(); + } + + private function anotherMethod() + { + $this->assertTrue(true); + } +} diff --git a/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithStaticAssert.php b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithStaticAssert.php new file mode 100644 index 000000000000..2767ec3f7960 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/ClassMethod/AddDoesNotPerformAssertionToNonAssertingTestRector/Source/AbstractClassWithStaticAssert.php @@ -0,0 +1,18 @@ +removedAndAddedFilesCollector->getRemovedFiles() as $smartFileInfo) { + $relativePath = $smartFileInfo->getRelativeFilePathFromDirectory(getcwd()); + if ($this->configuration->isDryRun()) { - $this->symfonyStyle->warning(sprintf('File "%s" will be removed', $smartFileInfo->getRealPath())); + $this->symfonyStyle->warning(sprintf('File "%s" will be removed', $relativePath)); } else { - $this->symfonyStyle->warning(sprintf('File "%s" was removed', $smartFileInfo->getRealPath())); + $this->symfonyStyle->warning(sprintf('File "%s" was removed', $relativePath)); $this->filesystem->remove($smartFileInfo->getRealPath()); } } diff --git a/src/Application/RectorApplication.php b/src/Application/RectorApplication.php index 423863cf8711..567ccfb52be7 100644 --- a/src/Application/RectorApplication.php +++ b/src/Application/RectorApplication.php @@ -211,7 +211,8 @@ private function processFileInfo(SmartFileInfo $fileInfo): void private function advance(SmartFileInfo $smartFileInfo, string $phase): void { if ($this->symfonyStyle->isVerbose()) { - $this->symfonyStyle->writeln(sprintf('[%s] %s', $phase, $smartFileInfo->getRealPath())); + $relativeFilePath = $smartFileInfo->getRelativeFilePathFromDirectory(getcwd()); + $this->symfonyStyle->writeln(sprintf('[%s] %s', $phase, $relativeFilePath)); } elseif ($this->configuration->showProgressBar()) { $this->symfonyStyle->progressAdvance(); } diff --git a/src/Console/Command/ScreenFileCommand.php b/src/Console/Command/ScreenFileCommand.php index 10823edfee3e..358bda4b0a6f 100644 --- a/src/Console/Command/ScreenFileCommand.php +++ b/src/Console/Command/ScreenFileCommand.php @@ -7,8 +7,10 @@ use Nette\Utils\FileSystem; use PhpParser\Comment\Doc; use PhpParser\Node; +use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Stmt\ClassLike; +use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Nop; use PHPStan\Type\ObjectType; use PHPStan\Type\UnionType; @@ -156,7 +158,7 @@ private function decorateNodes(array $nodes): void { $this->callableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node): Node { // not useful - if ($node instanceof Node\Stmt\Expression) { + if ($node instanceof Expression) { $infoNode = $node->expr; } else { $infoNode = $node; @@ -171,7 +173,7 @@ private function decorateNodes(array $nodes): void $data['variable_name - $this->isName($node, "X")'] = $this->nameResolver->getName($infoNode); } - if ($infoNode instanceof Node\Expr\MethodCall) { + if ($infoNode instanceof MethodCall) { $data['method_variable_name - $this->isName($node->var, "X")'] = $this->nameResolver->getName( $infoNode->var ); diff --git a/src/NodeContainer/ParsedNodesByType.php b/src/NodeContainer/ParsedNodesByType.php index 4e657a0f721e..fa1de7028de2 100644 --- a/src/NodeContainer/ParsedNodesByType.php +++ b/src/NodeContainer/ParsedNodesByType.php @@ -308,6 +308,49 @@ public function findFunction(string $name): ?Function_ return $this->simpleParsedNodesByType[Function_::class][$name] ?? null; } + public function findClassMethodByMethodCall(MethodCall $methodCall): ?ClassMethod + { + /** @var string|null $className */ + $className = $methodCall->getAttribute(AttributeKey::CLASS_NAME); + if ($className === null) { + return null; + } + + $methodName = $this->nameResolver->getName($methodCall->name); + if ($methodName === null) { + return null; + } + + return $this->findMethod($methodName, $className); + } + + public function findClassMethodByStaticCall(StaticCall $staticCall): ?ClassMethod + { + $methodName = $this->nameResolver->getName($staticCall->name); + if ($methodName === null) { + return null; + } + + $objectType = $this->nodeTypeResolver->getObjectType($staticCall->class); + if ($objectType instanceof ObjectType) { + return $this->findMethod($methodName, $objectType->getClassName()); + } + + if ($objectType instanceof UnionType) { + foreach ($objectType->getTypes() as $unionedType) { + if (! $unionedType instanceof ObjectType) { + continue; + } + $foundMethod = $this->findMethod($methodName, $unionedType->getClassName()); + if ($foundMethod) { + return $foundMethod; + } + } + } + + return null; + } + public function findMethod(string $methodName, string $className): ?ClassMethod { if (isset($this->methodsByType[$className][$methodName])) { diff --git a/src/Rector/AbstractPHPUnitRector.php b/src/Rector/AbstractPHPUnitRector.php index a9cae6df408e..797424c05ff7 100644 --- a/src/Rector/AbstractPHPUnitRector.php +++ b/src/Rector/AbstractPHPUnitRector.php @@ -4,13 +4,33 @@ namespace Rector\Rector; +use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; +use PhpParser\Node\Stmt\ClassMethod; use Rector\NodeTypeResolver\Node\AttributeKey; abstract class AbstractPHPUnitRector extends AbstractRector { + protected function isTestClassMethod(ClassMethod $classMethod): bool + { + if (! $classMethod->isPublic()) { + return false; + } + + if ($this->isName($classMethod, 'test*')) { + return true; + } + + $docComment = $classMethod->getDocComment(); + if ($docComment) { + return (bool) Strings::match($docComment->getText(), '#@test\b#'); + } + + return false; + } + protected function isPHPUnitMethodName(Node $node, string $name): bool { if (! $this->isPHPUnitTestCaseCall($node)) { diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php index a0e4ed829185..87dea79f524d 100644 --- a/src/Rector/AbstractRector.php +++ b/src/Rector/AbstractRector.php @@ -78,7 +78,8 @@ final public function enterNode(Node $node) // show current Rector class on --debug if ($this->symfonyStyle->isDebug()) { - $this->symfonyStyle->writeln('[applying] ' . static::class); + // indented on purpose to improve log nesting under [refactoring] + $this->symfonyStyle->writeln(' [applying] ' . static::class); } // already removed diff --git a/src/Reflection/ClassMethodReflectionFactory.php b/src/Reflection/ClassMethodReflectionFactory.php new file mode 100644 index 000000000000..f92f6957bb16 --- /dev/null +++ b/src/Reflection/ClassMethodReflectionFactory.php @@ -0,0 +1,52 @@ +createReflectionMethodIfExists($type->getFullyQualifiedName(), $methodName); + } + + if ($type instanceof ObjectType) { + return $this->createReflectionMethodIfExists($type->getClassName(), $methodName); + } + + if ($type instanceof UnionType || $type instanceof IntersectionType) { + foreach ($type->getTypes() as $unionedType) { + if (! $unionedType instanceof ObjectType) { + continue; + } + + $methodReflection = $this->createFromPHPStanTypeAndMethodName($unionedType, $methodName); + if ($methodReflection === null) { + continue; + } + + return $methodReflection; + } + } + + return null; + } + + private function createReflectionMethodIfExists(string $class, string $method): ?ReflectionMethod + { + if (! method_exists($class, $method)) { + return null; + } + + return new ReflectionMethod($class, $method); + } +} diff --git a/src/Reflection/FunctionReflectionResolver.php b/src/Reflection/FunctionReflectionResolver.php deleted file mode 100644 index 5cd02572f36e..000000000000 --- a/src/Reflection/FunctionReflectionResolver.php +++ /dev/null @@ -1,25 +0,0 @@ -signatureMapProvider = $signatureMapProvider; - } - - public function isPhpNativeFunction(string $functionName): bool - { - return $this->signatureMapProvider->hasFunctionSignature($functionName); - } -}