diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml index 9e9db1ff..b8a8e436 100644 --- a/.github/workflows/code_analysis.yaml +++ b/.github/workflows/code_analysis.yaml @@ -18,7 +18,11 @@ jobs: actions: - name: 'Active Classes' - run: vendor/bin/class-leak check config bin src rules --skip-type="Rector\PhpSpecToPHPUnit\Rector\Class_\ImplicitLetInitializationRector" --skip-type="Rector\PhpSpecToPHPUnit\Rector\Class_\LearnFromPHPUnitReportRector" + run: vendor/bin/class-leak check config bin src rules --skip-type="Rector\PhpSpecToPHPUnit\Rector\Class_\ImplicitLetInitializationRector" + + - + name: "Composer Analyser" + run: vendor/bin/composer-dependency-analyser name: ${{ matrix.actions.name }} runs-on: ubuntu-latest diff --git a/composer-dependency-analyser.php b/composer-dependency-analyser.php new file mode 100644 index 00000000..8f8085bd --- /dev/null +++ b/composer-dependency-analyser.php @@ -0,0 +1,9 @@ +ignoreErrorsOnPackage('rector/rector', [ErrorType::DEV_DEPENDENCY_IN_PROD]); diff --git a/composer.json b/composer.json index 9b74bc36..2a71fd3f 100644 --- a/composer.json +++ b/composer.json @@ -10,20 +10,19 @@ "require": { "php": ">=8.2", "symfony/finder": "^7.0", - "tomasvotruba/phpunit-json-result-printer": "^0.1.1", - "webmozart/assert": "^1.11", "nette/utils": "^4.0", - "symfony/console": "^7.1" + "symfony/console": "^7.1", + "nikic/php-parser": "^5.4", + "phpstan/phpstan": "^2.1" }, "require-dev": { "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", "tomasvotruba/class-leak": "^2.0", - "tracy/tracy": "^2.10" + "tracy/tracy": "^2.10", + "shipmonk/composer-dependency-analyser": "^1.8" }, "autoload": { "psr-4": { diff --git a/rector.php b/rector.php index e4948dd2..a4b244dc 100644 --- a/rector.php +++ b/rector.php @@ -9,7 +9,11 @@ ->withImportNames() ->withPaths([__DIR__ . '/config', __DIR__ . '/src', __DIR__ . '/rules', __DIR__ . '/rules-tests']) ->withSkip([ - StringClassNameToClassConstantRector::class => [__DIR__ . '/src/DocFactory.php'], + StringClassNameToClassConstantRector::class => [ + __DIR__ . '/src/DocFactory.php', + __DIR__ . '/src/NodeFactory/LetMockNodeFactory.php', + __DIR__ . '/rules/Rector/Class_/PhpSpecClassToPHPUnitClassRector.php', + ], ]) ->withPreparedSets( instanceOf: true, diff --git a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Fixture/more_than_once.php.inc b/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Fixture/more_than_once.php.inc deleted file mode 100644 index ca0b7892..00000000 --- a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Fixture/more_than_once.php.inc +++ /dev/null @@ -1,31 +0,0 @@ -someMock->expect($this->once())->method('someMethod'); - } -} - -?> ------ -someMock->expect($this->atLeastOnce())->method('someMethod'); - } -} - -?> diff --git a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Fixture/not_at_all.php.inc b/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Fixture/not_at_all.php.inc deleted file mode 100644 index 557be3ac..00000000 --- a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Fixture/not_at_all.php.inc +++ /dev/null @@ -1,30 +0,0 @@ -someMock->expect($this->once())->method('callMe'); - } -} - -?> ------ - diff --git a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/LearnFromPHPUintReportRectorTest.php b/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/LearnFromPHPUintReportRectorTest.php deleted file mode 100644 index a86a1d46..00000000 --- a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/LearnFromPHPUintReportRectorTest.php +++ /dev/null @@ -1,28 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Source/phpunit-report.txt b/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Source/phpunit-report.txt deleted file mode 100644 index 29b9d1ed..00000000 --- a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/Source/phpunit-report.txt +++ /dev/null @@ -1,23 +0,0 @@ -There were 6 failures: - -1) Tests\App\Core\SomeTest::testSomething -App\Core\SomeEntity::isCreated(): bool . -was not expected to be called more than once -/var/www/project/src/SomeTest.php:61 -/var/www/project/src/SomeTest.php:39 -/var/www/project/spec-tests/App/SomeTest.php:84 -phpvfscomposer:///var/www/project/vendor/phpunit/phpunit/phpunit:106 - -2) Rector\PhpSpecToPHPUnit\Tests\Rector\Class_\LearnFromPHPUnitReportRector\Fixture\NotAtAll::testAnything -App\Core\SomeEntity::callMe(): bool was not expected to be called more than once. - -/var/www/project/src/SomeTest.php:61 -/var/www/project/src/SomeTest.php:39 -/var/www/project/spec-tests/App/SomeTest.php:98 -phpvfscomposer:///var/www/project/vendor/phpunit/phpunit/phpunit:106 - -3) Rector\PhpSpecToPHPUnit\Tests\Rector\Class_\LearnFromPHPUnitReportRector\Fixture\MoreThanOnce::testSomething -Expectation failed for method name is "someMethod" when invoked 1 time(s). -Method was expected to be called 1 times, actually called 0 times. - -phpvfscomposer:///var/www/project/vendor/phpunit/phpunit/phpunit:106 diff --git a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/config/configured_rule.php b/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/config/configured_rule.php deleted file mode 100644 index 668600d4..00000000 --- a/rules-tests/Rector/Class_/LearnFromPHPUnitReportRector/config/configured_rule.php +++ /dev/null @@ -1,12 +0,0 @@ -ruleWithConfiguration(LearnFromPHPUnitReportRector::class, [ - __DIR__ . '/../Source/phpunit-report.txt', - ]); -}; diff --git a/rules/Rector/Class_/LearnFromPHPUnitReportRector.php b/rules/Rector/Class_/LearnFromPHPUnitReportRector.php deleted file mode 100644 index 03d2e514..00000000 --- a/rules/Rector/Class_/LearnFromPHPUnitReportRector.php +++ /dev/null @@ -1,176 +0,0 @@ -> - */ - public function getNodeTypes(): array - { - return [Class_::class]; - } - - public function getRuleDefinition(): RuleDefinition - { - return new RuleDefinition( - 'Update $this->once() and $this->atLeastOnce() based on a PHPUint error report file contents', - [ - new CodeSample( - <<<'CODE_SAMPLE' -use PHPUnit\Framework\TestCase; - -final class SomeTest extends TestCase -{ - public function testSomething() - { - $this->someMock->expect($this->once())->method('someMethod'); - } -} -CODE_SAMPLE - , - <<<'CODE_SAMPLE' -use PHPUnit\Framework\TestCase; - -final class SomeTest extends TestCase -{ - public function testSomething() - { - $this->someMock->expect($this->atLeastOnce())->method('someMethod'); - } -} -CODE_SAMPLE - ), - ] - ); - } - - /** - * @param Class_ $node - */ - public function refactor(Node $node): Node - { - if ($this->phpunitReportFilePath === null) { - throw new ShouldNotHappenException(); - } - - // 1. first - $moreThanOnceTestErrors = $this->phpUnitResultAnalyzer->resolveMoreThanOnceTestErrors( - $this->phpunitReportFilePath - ); - - foreach ($moreThanOnceTestErrors as $moreThanOnceTestError) { - $testClassMethod = $this->matchTestClassMethod($node, $moreThanOnceTestError); - if (! $testClassMethod instanceof ClassMethod) { - continue; - } - - foreach ((array) $testClassMethod->stmts as $key => $classMethodStmt) { - if (! MethodCallFinder::hasMethodMockByName( - $classMethodStmt, - $moreThanOnceTestError->getMockedMethod() - )) { - continue; - } - - unset($testClassMethod->stmts[$key]); - } - } - - // 2. second - $noCallsTestErrors = $this->phpUnitResultAnalyzer->resolveNoCallTestErrors($this->phpunitReportFilePath); - - foreach ($noCallsTestErrors as $noCallTestError) { - $testClassMethod = $this->matchTestClassMethod($node, $noCallTestError); - if (! $testClassMethod instanceof ClassMethod) { - continue; - } - - foreach ((array) $testClassMethod->stmts as $classMethodStmt) { - if (! MethodCallFinder::hasMethodMockByName($classMethodStmt, $noCallTestError->getMockedMethod())) { - continue; - } - - // replace $this->once() with $this->atLeastOnce() - $this->replaceThisOnceWithAtLeastOnce($classMethodStmt); - } - } - - return $node; - } - - /** - * @param string[] $configuration - */ - public function configure(array $configuration): void - { - // set path to file report - Assert::count($configuration, 1); - $phpunitReportFilePath = $configuration[0]; - - Assert::string($phpunitReportFilePath); - Assert::fileExists($phpunitReportFilePath); - - $this->phpunitReportFilePath = $phpunitReportFilePath; - } - - private function matchTestClassMethod( - Class_ $class, - NoCallTestError|MoreThanOnceTestError $noCallsTestError - ): ?ClassMethod { - // are we in the right test class? - if (! $this->isName($class, $noCallsTestError->getTestClass())) { - return null; - } - - return $class->getMethod($noCallsTestError->getTestClassMethod()); - } - - private function replaceThisOnceWithAtLeastOnce(Stmt $stmt): void - { - $this->traverseNodesWithCallable($stmt, function (Node $node): ?Node { - if (! $node instanceof MethodCall) { - return null; - } - - if (! $this->isName($node->name, PHPUnitMethodName::ONCE)) { - return null; - } - - $node->name = new Identifier(PHPUnitMethodName::AT_LEAST_ONCE); - return $node; - }); - } -} diff --git a/rules/Rector/Class_/PhpSpecClassToPHPUnitClassRector.php b/rules/Rector/Class_/PhpSpecClassToPHPUnitClassRector.php index 1ce7d150..342f1464 100644 --- a/rules/Rector/Class_/PhpSpecClassToPHPUnitClassRector.php +++ b/rules/Rector/Class_/PhpSpecClassToPHPUnitClassRector.php @@ -8,7 +8,6 @@ use PhpParser\Node\Identifier; use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Stmt\Class_; -use PHPUnit\Framework\TestCase; use Rector\PhpSpecToPHPUnit\Naming\PhpSpecRenaming; use Rector\Privatization\NodeManipulator\VisibilityManipulator; use Rector\Rector\AbstractRector; @@ -49,7 +48,7 @@ public function refactor(Node $node): ?Node // rename class and parent class $phpunitTestClassName = $this->phpSpecRenaming->createPHPUnitTestClassName($node); $node->name = new Identifier($phpunitTestClassName); - $node->extends = new FullyQualified(TestCase::class); + $node->extends = new FullyQualified('PHPUnit\Framework\TestCase'); $this->visibilityManipulator->makeFinal($node); diff --git a/rules/Rector/Class_/PromisesToAssertsRector.php b/rules/Rector/Class_/PromisesToAssertsRector.php index ceb701cc..a982ea1f 100644 --- a/rules/Rector/Class_/PromisesToAssertsRector.php +++ b/rules/Rector/Class_/PromisesToAssertsRector.php @@ -4,8 +4,6 @@ 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; @@ -13,7 +11,9 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Identifier; use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; use PhpParser\NodeFinder; use Rector\PhpSpecToPHPUnit\Enum\PhpSpecMethodName; diff --git a/src/Exception/ShouldNotHappenException.php b/src/Exception/ShouldNotHappenException.php new file mode 100644 index 00000000..83122917 --- /dev/null +++ b/src/Exception/ShouldNotHappenException.php @@ -0,0 +1,11 @@ +class instanceof Class_) { - throw new ShouldNotHappenException(); + throw new \Rector\PhpSpecToPHPUnit\Exception\ShouldNotHappenException(); } $arg = new Arg(new ClassConstFetch($expr->class, 'class')); diff --git a/src/NodeFactory/LetMockNodeFactory.php b/src/NodeFactory/LetMockNodeFactory.php index e37709eb..f26a67e2 100644 --- a/src/NodeFactory/LetMockNodeFactory.php +++ b/src/NodeFactory/LetMockNodeFactory.php @@ -15,8 +15,6 @@ use PhpParser\Node\Stmt\Expression; use PhpParser\Node\Stmt\Property; use PHPStan\Type\ObjectType; -use PHPUnit\Framework\MockObject\MockObject; -use Rector\Exception\ShouldNotHappenException; use Rector\NodeNameResolver\NodeNameResolver; use Rector\PhpParser\Node\NodeFactory; use Rector\PhpSpecToPHPUnit\DocFactory; @@ -43,7 +41,7 @@ public function createMockProperties(array $params): array $mockProperty = $this->crateMockProperty($parameterName); if (! $param->type instanceof Name) { - throw new ShouldNotHappenException(); + throw new \Rector\PhpSpecToPHPUnit\Exception\ShouldNotHappenException(); } $mockedClass = $param->type->toString(); @@ -69,7 +67,7 @@ public function createMockPropertyAssignExpressions(array $params): array foreach ($params as $param) { $parameterName = $this->createMockVariableName($param); if (! $param->type instanceof Name) { - throw new ShouldNotHappenException(); + throw new \Rector\PhpSpecToPHPUnit\Exception\ShouldNotHappenException(); } $mockPropertyFetch = new PropertyFetch(new Variable('this'), new Identifier($parameterName)); @@ -88,8 +86,7 @@ public function createMockPropertyAssignExpressions(array $params): array private function crateMockProperty(string $parameterName): Property { - $objectType = new ObjectType(MockObject::class); - + $objectType = new ObjectType('PHPUnit\Framework\MockObject\MockObject'); return $this->nodeFactory->createPrivatePropertyFromNameAndType($parameterName, $objectType); } diff --git a/src/PHPUnit/PHPUnitResultAnalyzer.php b/src/PHPUnit/PHPUnitResultAnalyzer.php deleted file mode 100644 index 6df3fc17..00000000 --- a/src/PHPUnit/PHPUnitResultAnalyzer.php +++ /dev/null @@ -1,127 +0,0 @@ -moreThanOnceTestErrors !== []) { - return $this->moreThanOnceTestErrors; - } - - $testErrors = $this->matchTestErrors($phpunitResultFilePath); - - $this->moreThanOnceTestErrors = $this->matchMoreThanOnceTestErrors($testErrors); - return $this->moreThanOnceTestErrors; - } - - /** - * @return NoCallTestError[] - */ - public function resolveNoCallTestErrors(string $phpunitResultFilePath): array - { - $testErrors = $this->matchTestErrors($phpunitResultFilePath); - - if ($this->noCallTestErrors !== []) { - return $this->noCallTestErrors; - } - - $this->noCallTestErrors = $this->matchNoCallTestErrors($testErrors); - return $this->noCallTestErrors; - } - - /** - * @return TestError[] - */ - private function matchTestErrors(string $phpunitResultFilePath): array - { - $phpunitResultFileContents = FileSystem::read($phpunitResultFilePath); - - $testErrors = []; - - $matches = Strings::matchAll($phpunitResultFileContents, '#^(\d+\) .*?):\d+\n#ms'); - foreach ($matches as $match) { - $testErrors[] = new TestError($match[1]); - } - - return $testErrors; - } - - /** - * @param TestError[] $testErrors - * @return MoreThanOnceTestError[] - */ - private function matchMoreThanOnceTestErrors(array $testErrors): array - { - $moreThanOnceTestErrors = []; - - foreach ($testErrors as $testError) { - $match = Strings::match( - $testError->getErrorContents(), - '#(?[\w\\\\]+?)::(?\w+)\n.*?(?\w+)\(\).*?\s+was not expected to be called more than once#s' - ); - - if ($match === null) { - continue; - } - - $moreThanOnceTestErrors[] = new MoreThanOnceTestError( - $match['test_class'], - $match['test_method_name'], - $match['mocked_method_name'] - ); - } - - return $moreThanOnceTestErrors; - } - - /** - * @param TestError[] $testErrors - * @return NoCallTestError[] - */ - private function matchNoCallTestErrors(array $testErrors): array - { - $noCallTestErrors = []; - - foreach ($testErrors as $testError) { - $match = Strings::match( - $testError->getErrorContents(), - '#(?[\w\\\\]+?)::(?\w+)\s+Expectation failed for method name is "(?.*?)" when .*?Method was expected to be called 1 times, actually called 0 times#s' - ); - - if ($match === null) { - continue; - } - - $noCallTestErrors[] = new NoCallTestError( - $match['test_class'], - $match['test_method_name'], - $match['mocked_method_name'] - ); - } - - return $noCallTestErrors; - } -} diff --git a/src/ServiceMockResolver.php b/src/ServiceMockResolver.php index e0ff5023..648c8982 100644 --- a/src/ServiceMockResolver.php +++ b/src/ServiceMockResolver.php @@ -7,7 +7,6 @@ use PhpParser\Node\Name; use PhpParser\Node\Param; use PhpParser\Node\Stmt\ClassMethod; -use Rector\Exception\ShouldNotHappenException; use Rector\NodeNameResolver\NodeNameResolver; use Rector\PhpSpecToPHPUnit\ValueObject\ServiceMock; @@ -46,7 +45,10 @@ private function createServiceMock(Param $param): ServiceMock // this should be always typed if (! $param->type instanceof Name) { - throw new ShouldNotHappenException(sprintf('Param "%s" must be typed', $variableName)); + throw new \Rector\PhpSpecToPHPUnit\Exception\ShouldNotHappenException(sprintf( + 'Param "%s" must be typed', + $variableName + )); } $mockClassName = $param->type->toString(); diff --git a/src/ValueObject/DuringAndRelatedMethodCall.php b/src/ValueObject/DuringAndRelatedMethodCall.php index 7b718beb..e9bf70df 100644 --- a/src/ValueObject/DuringAndRelatedMethodCall.php +++ b/src/ValueObject/DuringAndRelatedMethodCall.php @@ -61,7 +61,6 @@ public function getCalledArgs(): array } $builderFactory = new BuilderFactory(); - $array = $secondArg->value; $values = []; diff --git a/src/ValueObject/PHPUnit/MoreThanOnceTestError.php b/src/ValueObject/PHPUnit/MoreThanOnceTestError.php deleted file mode 100644 index 34314464..00000000 --- a/src/ValueObject/PHPUnit/MoreThanOnceTestError.php +++ /dev/null @@ -1,33 +0,0 @@ -testClass; - } - - public function getTestClassMethod(): string - { - return $this->testClassMethod; - } - - public function getMockedMethod(): string - { - return $this->mockedMethod; - } -} diff --git a/src/ValueObject/PHPUnit/NoCallTestError.php b/src/ValueObject/PHPUnit/NoCallTestError.php deleted file mode 100644 index f2118ae8..00000000 --- a/src/ValueObject/PHPUnit/NoCallTestError.php +++ /dev/null @@ -1,33 +0,0 @@ -testClass; - } - - public function getTestClassMethod(): string - { - return $this->testClassMethod; - } - - public function getMockedMethod(): string - { - return $this->mockedMethod; - } -} diff --git a/src/ValueObject/PHPUnit/TestError.php b/src/ValueObject/PHPUnit/TestError.php deleted file mode 100644 index c60196d1..00000000 --- a/src/ValueObject/PHPUnit/TestError.php +++ /dev/null @@ -1,18 +0,0 @@ -errorContents; - } -}