diff --git a/lib/Transunit/Pass/AddTestMethodPrefixPass.php b/lib/Transunit/Pass/AddTestMethodPrefixPass.php index 9c522d4..2be5471 100644 --- a/lib/Transunit/Pass/AddTestMethodPrefixPass.php +++ b/lib/Transunit/Pass/AddTestMethodPrefixPass.php @@ -45,7 +45,7 @@ private function addPrefix(Node\Stmt\ClassMethod $node): void if ( 0 !== strpos($methodName, 'it_') - || 0 !== strpos($methodName, 'its_') + && 0 !== strpos($methodName, 'its_') ) { return; } diff --git a/lib/Transunit/Pass/AssertionPass.php b/lib/Transunit/Pass/AssertionPass.php index 4902441..2e3d0d2 100644 --- a/lib/Transunit/Pass/AssertionPass.php +++ b/lib/Transunit/Pass/AssertionPass.php @@ -31,7 +31,6 @@ public function rewrite(Node $node): void return; } - $assertion = $node->expr->name->toString(); $mappedAssertions = [ 'shouldBe' => 'assertSame', 'shouldReturn' => 'assertSame', @@ -41,6 +40,14 @@ public function rewrite(Node $node): void 'shouldImplement' => 'assertInstanceOf', ]; + $mappedConstantAssertions = [ + 'null' => 'assertNull', + 'true' => 'assertTrue', + 'false' => 'assertFalse', + ]; + + $assertion = $node->expr->name->toString(); + if (!isset($mappedAssertions[$assertion])) { return; } @@ -48,19 +55,20 @@ public function rewrite(Node $node): void $expectation = $node->expr->args[0]->value; $call = $node->expr->var; + if ( $expectation instanceof Node\Expr\ConstFetch - && $expectation->name->toString() === 'null' + && isset($mappedConstantAssertions[$expectation->name->toString()]) ) { - // static::assertNull($call); + $assertionMethod = $mappedConstantAssertions[$expectation->name->toString()] ?? null; + $rewrittenAssertion = new Node\Expr\StaticCall( new Node\Name('static'), - 'assertNull', + $assertionMethod, [ new Node\Arg($call) ] ); - } else { // static::assertSame($expectation, $call); $rewrittenAssertion = new Node\Expr\StaticCall( diff --git a/lib/Transunit/Pass/DeclareTestSubjectPropertyPass.php b/lib/Transunit/Pass/DeclareTestSubjectPropertyPass.php index bced201..57bcada 100644 --- a/lib/Transunit/Pass/DeclareTestSubjectPropertyPass.php +++ b/lib/Transunit/Pass/DeclareTestSubjectPropertyPass.php @@ -29,11 +29,7 @@ public function rewrite(Node $node): void return; } - $useProphecyTrait = array_shift($node->stmts); - $this->declareTestSubjectAsClassProperty($node); - - array_unshift($node->stmts, $useProphecyTrait); } private function declareTestSubjectAsClassProperty(Node\Stmt\Class_ $node): void diff --git a/lib/Transunit/Pass/ImportSubjectClassPass.php b/lib/Transunit/Pass/ImportSubjectClassPass.php index 40c6613..71ae128 100644 --- a/lib/Transunit/Pass/ImportSubjectClassPass.php +++ b/lib/Transunit/Pass/ImportSubjectClassPass.php @@ -44,6 +44,17 @@ private function importSubjectClass(Namespace_ $node): void if ($ns[0] === 'tests' && $ns[1] === 'unit') { array_shift($ns); array_shift($ns); + } elseif ($ns[0] === 'spec') { + array_shift($ns); + } + + foreach ($node->stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Class_) { + $testClassname = $stmt->name->toString(); + $ns[] = substr($testClassname, 0, -4); + + break; + } } $fcqn = implode('\\', $ns); @@ -59,15 +70,6 @@ private function importSubjectClass(Namespace_ $node): void } } - foreach ($node->stmts as $stmt) { - if ($stmt instanceof Node\Stmt\Class_) { - $testClassname = $stmt->name->toString(); - $ns[] = substr($testClassname, 0, -4); - - break; - } - } - $use = new Node\Stmt\Use_([ new Node\Stmt\UseUse(new Name($fcqn)), ]); diff --git a/lib/Transunit/Pass/InitializeTestSubjectPass.php b/lib/Transunit/Pass/InitializeTestSubjectPass.php index ee23b43..cbc9533 100644 --- a/lib/Transunit/Pass/InitializeTestSubjectPass.php +++ b/lib/Transunit/Pass/InitializeTestSubjectPass.php @@ -8,17 +8,10 @@ /** * ``` - * class TestSubjectTest extends TestCase - * { - * use ProphecyTrait; - * - * private TestSubject $_testSubject; - * - * - function let(AgentRepository $agentRepository, EventDispatcher $eventDispatcher) - * + function let() + * function let(AgentRepository $agentRepository, EventDispatcher $eventDispatcher) * { - * - $this->beConstructedWith($agentRepository, $eventDispatcher); - * + $this->_testSubject = new TestSubject($this->agentRepository->reveal(), $this->eventDispatcher->reveal()); + * - $this->beConstructedWith($agentRepository, $eventDispatcher, 'chicken'); + * + $this->_testSubject = new TestSubject($this->agentRepository->reveal(), $this->eventDispatcher->reveal(), 'chicken'); * } * ``` */ @@ -43,7 +36,10 @@ public function rewrite(Node $node): void $useProphecyTrait = array_shift($node->stmts); - $this->instantiateTestSubject($setupMethod); + $testClassname = $node->name->toString(); + $subjectClassname = substr($testClassname, 0, -4); + + $this->instantiateTestSubject($setupMethod, $subjectClassname); array_unshift($node->stmts, $useProphecyTrait); } @@ -62,54 +58,38 @@ private function findSetupMethod(Node\Stmt\Class_ $node): ?Node\Stmt\ClassMethod return null; } - private function instantiateTestSubject(Node\Stmt\ClassMethod $node): void + private function instantiateTestSubject(Node\Stmt\ClassMethod $node, string $subjectClassname): void { - $testClassname = $node->name->toString(); - $subjectClassname = substr($testClassname, 0, -4); - - $collaboratorNames = []; + /** @var Node\Arg[] $args */ + $args = []; foreach ($node->stmts as $stmt) { - // Check if expression is a property assignment if (! $stmt instanceof Node\Stmt\Expression) { continue; } - if (! $stmt->expr instanceof Node\Expr\Assign) { - continue; - } - - if (! $stmt->expr->var instanceof Node\Expr\PropertyFetch) { - continue; - } - - if (! $stmt->expr->var->var instanceof Node\Expr\Variable) { + if ( + $stmt->expr instanceof Node\Expr\Assign + && $stmt->expr->expr instanceof Node\Expr\New_ + && $stmt->expr->var instanceof Node\Expr\PropertyFetch + && $stmt->expr->var->name instanceof Node\Identifier + && '_testSubject' === $stmt->expr->var->name->name + ) { + // Already instantiated. continue; } - $collaboratorVariableName = $stmt->expr->var->var->name; - - if ($collaboratorVariableName === 'this') { - continue; + if ( + $stmt->expr instanceof Node\Expr\MethodCall + && $stmt->expr->name instanceof Node\Identifier + && 'beConstructedWith' === $stmt->expr->name->name + ) { + $args = $stmt->expr->args; + break; } - - $collaboratorNames[$collaboratorVariableName] = true; - } - - $globalCollaborators = []; - - foreach ($collaboratorNames as $collaborator => $v) { - // call $this->{$collaborator}->reveal() - $globalCollaborators[] = new Node\Arg(new Node\Expr\MethodCall( - new Node\Expr\PropertyFetch( - new Node\Expr\Variable('this'), - $collaborator - ), - 'reveal' - )); } - $node->stmts = array_map(function ($stmt) use ($subjectClassname, $globalCollaborators) { + $node->stmts = array_map(function ($stmt) use ($subjectClassname, $args) { if ($stmt instanceof Node\Stmt\Expression && $stmt->expr instanceof Node\Expr\MethodCall && $stmt->expr->var instanceof Node\Expr\Variable @@ -117,14 +97,14 @@ private function instantiateTestSubject(Node\Stmt\ClassMethod $node): void && $stmt->expr->name->toString() === 'beConstructedWith' ) { // replace $this->beConstructedWith(...) with $this->_testSubject = new TestSubject(...) - return $this->writeInstantiation($subjectClassname, $globalCollaborators); + return $this->writeInstantiation($subjectClassname, $args); } return $stmt; }, $node->stmts); } - private function writeInstantiation(string $subjectClassname, array $globalCollaborators = []): Node\Stmt\Expression + private function writeInstantiation(string $subjectClassname, array $args = []): Node\Stmt\Expression { return new Node\Stmt\Expression( new Node\Expr\Assign( @@ -134,7 +114,7 @@ private function writeInstantiation(string $subjectClassname, array $globalColla ), new Node\Expr\New_( new Node\Name($subjectClassname), - $globalCollaborators + $args ) ) ); diff --git a/lib/Transunit/Pass/RenameSetupPass.php b/lib/Transunit/Pass/RenameSetupPass.php index 816eaf1..ab5a870 100644 --- a/lib/Transunit/Pass/RenameSetupPass.php +++ b/lib/Transunit/Pass/RenameSetupPass.php @@ -23,7 +23,10 @@ class RenameSetupPass implements Pass public function find(NodeFinder $nodeFinder, $ast): array { return $nodeFinder->find($ast, function (Node $node) { - if ($node instanceof Node\Stmt\ClassMethod && !in_array($node->name->toString(), ['setUp', 'let'],true)) { + if ( + $node instanceof Node\Stmt\ClassMethod + && 'let' === $node->name->toString() + ) { return $node; } diff --git a/lib/Transunit/Pass/RevealPass.php b/lib/Transunit/Pass/RevealPass.php new file mode 100644 index 0000000..a9a1a91 --- /dev/null +++ b/lib/Transunit/Pass/RevealPass.php @@ -0,0 +1,103 @@ +findFirstInstanceOf($ast, Node\Stmt\Class_::class)]; + } + + public function rewrite(Node $node): void + { + if (!$node instanceof Node\Stmt\Class_) { + return; + } + + $setupMethod = $this->findSetupMethod($node); + + if (!$setupMethod instanceof Node\Stmt\ClassMethod) { + return; + } + + $useProphecyTrait = array_shift($node->stmts); + + $testClassname = $node->name->toString(); + $subjectClassname = substr($testClassname, 0, -4); + + $this->instantiateTestSubject($setupMethod, $subjectClassname); + + array_unshift($node->stmts, $useProphecyTrait); + } + + private function findSetupMethod(Node\Stmt\Class_ $node): ?Node\Stmt\ClassMethod + { + foreach ($node->stmts as $stmt) { + if ( + $stmt instanceof Node\Stmt\ClassMethod + && in_array($stmt->name->toString(), ['setUp', 'let'], true) + ) { + return $stmt; + } + } + + return null; + } + + private function instantiateTestSubject(Node\Stmt\ClassMethod $node, string $subjectClassname): void + { + $rewrittenNode = null; + + foreach ($node->stmts as $stmt) { + if (! $stmt instanceof Node\Stmt\Expression) { + continue; + } + + if ( + $stmt->expr instanceof Node\Expr\Assign + && $stmt->expr->expr instanceof Node\Expr\New_ + && $stmt->expr->var instanceof Node\Expr\PropertyFetch + && $stmt->expr->var->name instanceof Node\Identifier + && '_testSubject' === $stmt->expr->var->name->name + ) { + $rewrittenNode = $stmt->expr->expr; + break; + } + + if ( + $stmt->expr instanceof Node\Expr\MethodCall + && $stmt->expr->name instanceof Node\Identifier + && 'beConstructedWith' === $stmt->expr->name->name + ) { + $rewrittenNode = $stmt->expr; + break; + } + } + + $newArgs = []; + + foreach ($rewrittenNode->args as $arg) { + if (!$arg->value instanceof Node\Expr\Variable) { + $newArgs[] = $arg; + continue; + } + + // @todo Confirm the variable was declared as an argument of the let() method + + $newArgs[] = new Node\Arg(new Node\Expr\MethodCall( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + $arg->value->name + ), + 'reveal' + )); + } + + $rewrittenNode->args = $newArgs; + } +} diff --git a/lib/Transunit/Transunit.php b/lib/Transunit/Transunit.php index 7898fb4..11fcc21 100644 --- a/lib/Transunit/Transunit.php +++ b/lib/Transunit/Transunit.php @@ -60,17 +60,18 @@ private static function processFile(string $path): string new Pass\ImportMockingLibraryPass(), new Pass\RenameClassPass(), new Pass\ChangeExtendedClassPass(), - new Pass\UseProphecyTraitPass(), - new Pass\GlobalCollaboratorPass(), - new Pass\CreateSetupIfNoneExistsPass(), - new Pass\InitializeTestSubjectPass(), - new Pass\DeclareTestSubjectPropertyPass(), - new Pass\CallTestSubjectPass(), - new Pass\AssertionPass(), + new Pass\DeclareTestSubjectPropertyPass(), // run before CreateSetupIfNoneExistsPass. + new Pass\CreateSetupIfNoneExistsPass(), // run after DeclareTestSubjectPropertyPass and before UseProphecyTraitPass + new Pass\UseProphecyTraitPass(), // run after CreateSetupIfNoneExistsPass new Pass\RenameSetupPass(), new Pass\AddTestMethodPrefixPass(), - new Pass\ProphesizeGlobalCollaboratorsPass(), - new Pass\ProphesizeLocalCollaboratorsPass(), + // new Pass\GlobalCollaboratorPass(), + new Pass\InitializeTestSubjectPass(), + new Pass\RevealPass(), + new Pass\CallTestSubjectPass(), // run before CallTestSubjectPass + new Pass\AssertionPass(), // run after CallTestSubjectPass. + // new Pass\ProphesizeGlobalCollaboratorsPass(), // run before ProphesizeLocalCollaboratorsPass + // new Pass\ProphesizeLocalCollaboratorsPass(), // run after ProphesizeGlobalCollaboratorsPass ]; /** @var Pass $pass */