diff --git a/composer.json b/composer.json index a8e77408e756..3c3e67f07206 100644 --- a/composer.json +++ b/composer.json @@ -43,14 +43,14 @@ "friendsofphp/php-cs-fixer": "^2.16", "ocramius/package-versions": "^1.4|^1.5", "phpunit/phpunit": "^8.5|^9.0", + "psr/event-dispatcher": "^1.0", "slam/phpstan-extensions": "^5.0", + "slevomat/coding-standard": "dev-master", "symplify/changelog-linker": "^8.0", "symplify/easy-coding-standard": "^8.0", "symplify/monorepo-builder": "^8.0", "symplify/phpstan-extensions": "^8.0", - "thecodingmachine/phpstan-strict-rules": "^0.12", - "psr/event-dispatcher": "^1.0", - "slevomat/coding-standard": "dev-master" + "thecodingmachine/phpstan-strict-rules": "^0.12" }, "replace": { "rector/rector-prefixed": "self.version" diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index e3a2cfd4b58e..39fd21df9118 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -7371,8 +7371,9 @@ each() function is deprecated, use key() and current() instead ```diff -list($key, $callback) = each($callbacks); -+$key = key($opt->option); -+$val = current($opt->option); ++$key = key($callbacks); ++$callback = current($callbacks); ++next($callbacks); ```
diff --git a/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/ArrayKeysAndInArrayToArrayKeyExistsRectorTest.php b/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/ArrayKeysAndInArrayToArrayKeyExistsRectorTest.php index 4ad8193d88d3..6899f6c75a90 100644 --- a/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/ArrayKeysAndInArrayToArrayKeyExistsRectorTest.php +++ b/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/ArrayKeysAndInArrayToArrayKeyExistsRectorTest.php @@ -6,9 +6,9 @@ use Iterator; use Rector\CodeQuality\Rector\FuncCall\ArrayKeysAndInArrayToArrayKeyExistsRector; -use Rector\Core\Testing\PHPUnit\AbstractRunnableRectorTestCase; +use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase; -final class ArrayKeysAndInArrayToArrayKeyExistsRectorTest extends AbstractRunnableRectorTestCase +final class ArrayKeysAndInArrayToArrayKeyExistsRectorTest extends AbstractRectorTestCase { /** * @dataProvider provideData() @@ -16,7 +16,6 @@ final class ArrayKeysAndInArrayToArrayKeyExistsRectorTest extends AbstractRunnab public function test(string $file): void { $this->doTestFile($file); - $this->assertOriginalAndFixedFileResultEquals($file); } public function provideData(): Iterator diff --git a/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/Fixture/fixture.php.inc b/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/Fixture/fixture.php.inc index 46d6fbbb255e..c46f8a024bf9 100644 --- a/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/Fixture/fixture.php.inc +++ b/rules/code-quality/tests/Rector/FuncCall/ArrayKeysAndInArrayToArrayKeyExistsRector/Fixture/fixture.php.inc @@ -2,16 +2,17 @@ namespace Rector\CodeQuality\Tests\Rector\FuncCall\ArrayKeysAndInArrayToArrayKeyExistsRector\Fixture; -use Rector\Core\Testing\PHPUnit\RunnableInterface; +use Rector\Core\Testing\Contract\RunnableInterface; class SomeClass implements RunnableInterface { public function run() { $packageName = "foo"; - $values = ["foo" => "bar"]; - + $values = ["foo" => "bar"]; + $keys = array_keys($values); + return in_array($packageName, $keys, true); } } @@ -22,14 +23,15 @@ class SomeClass implements RunnableInterface namespace Rector\CodeQuality\Tests\Rector\FuncCall\ArrayKeysAndInArrayToArrayKeyExistsRector\Fixture; -use Rector\Core\Testing\PHPUnit\RunnableInterface; +use Rector\Core\Testing\Contract\RunnableInterface; class SomeClass implements RunnableInterface { public function run() { $packageName = "foo"; - $values = ["foo" => "bar"]; + $values = ["foo" => "bar"]; + return array_key_exists($packageName, $values); } } diff --git a/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/ReplaceEventManagerWithEventSubscriberRectorTest.php b/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/ReplaceEventManagerWithEventSubscriberRectorTest.php index 1f68af95835d..7d2a9bc83261 100644 --- a/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/ReplaceEventManagerWithEventSubscriberRectorTest.php +++ b/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/ReplaceEventManagerWithEventSubscriberRectorTest.php @@ -13,7 +13,7 @@ public function test(): void { $this->doTestFile(__DIR__ . '/Fixture/fixture.php.inc'); - $expectedEventFilePath = dirname($this->originalTempFile) . '/Event/SomeClassCopyEvent.php'; + $expectedEventFilePath = $this->originalTempFileInfo->getPath() . '/Event/SomeClassCopyEvent.php'; $this->assertFileExists($expectedEventFilePath); $this->assertFileEquals(__DIR__ . '/Source/ExpectedSomeClassCopyEvent.php', $expectedEventFilePath); diff --git a/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/ReplaceMagicPropertyEventWithEventClassRectorTest.php b/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/ReplaceMagicPropertyEventWithEventClassRectorTest.php index 28e286156ebf..f71fd63f7ab5 100644 --- a/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/ReplaceMagicPropertyEventWithEventClassRectorTest.php +++ b/rules/nette-kdyby/tests/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/ReplaceMagicPropertyEventWithEventClassRectorTest.php @@ -18,7 +18,7 @@ public function testSimpleEvent(): void { $this->doTestFile(__DIR__ . '/Fixture/simple_event.php.inc'); - $expectedEventFilePath = dirname($this->originalTempFile) . '/Event/FileManagerUploadEvent.php'; + $expectedEventFilePath = $this->originalTempFileInfo->getPath() . '/Event/FileManagerUploadEvent.php'; $this->assertFileExists($expectedEventFilePath); $this->assertFileEquals(__DIR__ . '/Source/ExpectedFileManagerUploadEvent.php', $expectedEventFilePath); } @@ -27,7 +27,7 @@ public function testDuplicatedEventParams(): void { $this->doTestFile(__DIR__ . '/Fixture/duplicated_event_params.php.inc'); - $expectedEventFilePath = dirname($this->originalTempFile) . '/Event/DuplicatedEventParamsUploadEvent.php'; + $expectedEventFilePath = $this->originalTempFileInfo->getPath() . '/Event/DuplicatedEventParamsUploadEvent.php'; $this->assertFileExists($expectedEventFilePath); $this->assertFileEquals( __DIR__ . '/Source/ExpectedDuplicatedEventParamsUploadEvent.php', diff --git a/rules/php71/tests/Rector/BinaryOp/IsIterableRector/Fixture/polyfill_function.php.inc b/rules/php71/tests/Rector/BinaryOp/IsIterableRector/Fixture/polyfill_function.php.inc index a3eae8194ddb..e6afb08a3eb7 100644 --- a/rules/php71/tests/Rector/BinaryOp/IsIterableRector/Fixture/polyfill_function.php.inc +++ b/rules/php71/tests/Rector/BinaryOp/IsIterableRector/Fixture/polyfill_function.php.inc @@ -4,7 +4,7 @@ namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector\Fixture; use Traversable; -class PolyfillFunction +class IsIterablePolyfillFunction { public function run($foo) { @@ -28,7 +28,7 @@ namespace Rector\Php71\Tests\Rector\BinaryOp\IsIterableRector\Fixture; use Traversable; -class PolyfillFunction +class IsIterablePolyfillFunction { public function run($foo) { diff --git a/rules/php72/src/Rector/Each/ListEachRector.php b/rules/php72/src/Rector/Each/ListEachRector.php index 94bea7a46027..f21b6ac7fe35 100644 --- a/rules/php72/src/Rector/Each/ListEachRector.php +++ b/rules/php72/src/Rector/Each/ListEachRector.php @@ -8,7 +8,6 @@ use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\List_; -use PhpParser\Node\Stmt\Do_; use PhpParser\Node\Stmt\Expression; use Rector\Core\PhpParser\Node\Manipulator\AssignManipulator; use Rector\Core\Rector\AbstractRector; @@ -44,8 +43,9 @@ public function getDefinition(): RectorDefinition PHP , <<<'PHP' -$key = key($opt->option); -$val = current($opt->option); +$key = key($callbacks); +$callback = current($callbacks); +next($callbacks); PHP ), ] @@ -94,15 +94,13 @@ public function refactor(Node $node): ?Node // ↓ // $key = key($values); // $value = current($values); - // next($values); - only inside a loop + // next($values); $currentFuncCall = $this->createFuncCall('current', $eachFuncCall->args); $assignCurrentNode = new Assign($listNode->items[1]->value, $currentFuncCall); $this->addNodeAfterNode($assignCurrentNode, $node); - if ($this->isInsideDoWhile($node)) { - $nextFuncCall = $this->createFuncCall('next', $eachFuncCall->args); - $this->addNodeAfterNode($nextFuncCall, $node); - } + $nextFuncCall = $this->createFuncCall('next', $eachFuncCall->args); + $this->addNodeAfterNode($nextFuncCall, $node); $keyFuncCall = $this->createFuncCall('key', $eachFuncCall->args); return new Assign($listNode->items[0]->value, $keyFuncCall); @@ -130,19 +128,4 @@ private function shouldSkip(Assign $assign): bool // empty list → cannot handle return $listNode->items[0] === null && $listNode->items[1] === null; } - - /** - * Is inside the "do {} while ();" loop → need to add "next()" - */ - private function isInsideDoWhile(Node $assignNode): bool - { - $parentNode = $assignNode->getAttribute(AttributeKey::PARENT_NODE); - if (! $parentNode instanceof Expression) { - return false; - } - - $parentParentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE); - - return $parentParentNode instanceof Do_; - } } diff --git a/rules/php72/tests/Rector/Each/Fixture/fixture2.php.inc b/rules/php72/tests/Rector/Each/Fixture/fixture2.php.inc index 76151eee6d58..3fed9c3630ce 100644 --- a/rules/php72/tests/Rector/Each/Fixture/fixture2.php.inc +++ b/rules/php72/tests/Rector/Each/Fixture/fixture2.php.inc @@ -23,6 +23,7 @@ function each2 { $key = key($opt->option); $val = current($opt->option); + next($opt->option); $tid = key($option->option); diff --git a/rules/php72/tests/Rector/Each/Fixture/list_each_next.php.inc b/rules/php72/tests/Rector/Each/Fixture/list_each_next.php.inc new file mode 100644 index 000000000000..8c485138e71d --- /dev/null +++ b/rules/php72/tests/Rector/Each/Fixture/list_each_next.php.inc @@ -0,0 +1,47 @@ + 1, 'b' => 2]; + + list($key, $value) = each($parentArray); + + list($key2, $value2) = each($parentArray); + + return [$key, $value, $parentArray, $key2, $value2]; + } +} + +?> +----- + 1, 'b' => 2]; + + $key = key($parentArray); + $value = current($parentArray); + next($parentArray); + + $key2 = key($parentArray); + $value2 = current($parentArray); + next($parentArray); + + return [$key, $value, $parentArray, $key2, $value2]; + } +} + +?> diff --git a/src/Testing/PHPUnit/RunnableInterface.php b/src/Testing/Contract/RunnableInterface.php similarity index 69% rename from src/Testing/PHPUnit/RunnableInterface.php rename to src/Testing/Contract/RunnableInterface.php index 7ffab39a7a16..c1a68e9aec82 100644 --- a/src/Testing/PHPUnit/RunnableInterface.php +++ b/src/Testing/Contract/RunnableInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Core\Testing\PHPUnit; +namespace Rector\Core\Testing\Contract; interface RunnableInterface { diff --git a/src/Testing/PHPUnit/AbstractRectorTestCase.php b/src/Testing/PHPUnit/AbstractRectorTestCase.php index 552a60c3d274..035ff9a762c6 100644 --- a/src/Testing/PHPUnit/AbstractRectorTestCase.php +++ b/src/Testing/PHPUnit/AbstractRectorTestCase.php @@ -5,6 +5,7 @@ namespace Rector\Core\Testing\PHPUnit; use Nette\Utils\FileSystem; +use Nette\Utils\Strings; use PHPStan\Analyser\NodeScopeResolver; use PHPUnit\Framework\ExpectationFailedException; use Psr\Container\ContainerInterface; @@ -17,6 +18,7 @@ use Rector\Core\HttpKernel\RectorKernel; use Rector\Core\Stubs\StubLoader; use Rector\Core\Testing\Application\EnabledRectorsProvider; +use Rector\Core\Testing\Contract\RunnableInterface; use Rector\Core\Testing\Finder\RectorsFinder; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -27,6 +29,8 @@ abstract class AbstractRectorTestCase extends AbstractGenericRectorTestCase { + use RunnableRectorTrait; + /** * @var FileProcessor */ @@ -38,19 +42,19 @@ abstract class AbstractRectorTestCase extends AbstractGenericRectorTestCase protected $parameterProvider; /** - * @var string + * @var SmartFileInfo */ - protected $originalTempFile; + protected $originalTempFileInfo; /** - * @var bool + * @var FixtureSplitter */ - private $autoloadTestFixture = true; + protected $fixtureSplitter; /** - * @var FixtureSplitter + * @var bool */ - private $fixtureSplitter; + private $autoloadTestFixture = true; /** * @var Container|ContainerInterface|null @@ -130,21 +134,23 @@ protected function doTestFileWithoutAutoload(string $file): void protected function doTestFile(string $fixtureFile): void { - $smartFileInfo = new SmartFileInfo($fixtureFile); - [$originalFile, $changedFile] = $this->fixtureSplitter->splitContentToOriginalFileAndExpectedFile( - $smartFileInfo, + $fixtureFileInfo = new SmartFileInfo($fixtureFile); + + [$originalFileInfo, $expectedFileInfo] = $this->fixtureSplitter->splitContentToOriginalFileAndExpectedFile( + $fixtureFileInfo, $this->autoloadTestFixture ); - $this->nodeScopeResolver->setAnalysedFiles([$originalFile]); + $this->nodeScopeResolver->setAnalysedFiles([$originalFileInfo->getRealPath()]); - $this->doTestFileMatchesExpectedContent( - $originalFile, - $changedFile, - $smartFileInfo->getRelativeFilePathFromCwd() - ); + $this->doTestFileMatchesExpectedContent($originalFileInfo, $expectedFileInfo, $fixtureFileInfo); + + $this->originalTempFileInfo = $originalFileInfo; - $this->originalTempFile = $originalFile; + // runnable? + if (Strings::contains($originalFileInfo->getContents(), RunnableInterface::class)) { + $this->assertOriginalAndFixedFileResultEquals($originalFileInfo, $expectedFileInfo); + } } protected function getTempPath(): string @@ -238,39 +244,32 @@ private function configurePhpVersionFeatures(): void } private function doTestFileMatchesExpectedContent( - string $originalFile, - string $expectedFile, - string $fixtureFile + SmartFileInfo $originalFileInfo, + SmartFileInfo $expectedFileInfo, + SmartFileInfo $fixtureFileInfo ): void { - $this->setParameter(Option::SOURCE, [$originalFile]); - - $smartFileInfo = new SmartFileInfo($originalFile); + $this->setParameter(Option::SOURCE, [$originalFileInfo->getRealPath()]); // life-cycle trio :) - $this->fileProcessor->parseFileInfoToLocalCache($smartFileInfo); - $this->fileProcessor->refactor($smartFileInfo); + $this->fileProcessor->parseFileInfoToLocalCache($originalFileInfo); + $this->fileProcessor->refactor($originalFileInfo); - $changedContent = $this->fileProcessor->printToString($smartFileInfo); + $changedContent = $this->fileProcessor->printToString($originalFileInfo); - $causedByFixtureMessage = $this->createCausedByFixtureMessage($fixtureFile); + $causedByFixtureMessage = $fixtureFileInfo->getRelativeFilePathFromCwd(); $removedAndAddedFilesProcessor = self::$container->get(RemovedAndAddedFilesProcessor::class); $removedAndAddedFilesProcessor->run(); try { - $this->assertStringEqualsFile($expectedFile, $changedContent, $causedByFixtureMessage); + $this->assertStringEqualsFile($expectedFileInfo->getRealPath(), $changedContent, $causedByFixtureMessage); } catch (ExpectationFailedException $expectationFailedException) { - $expectedFileContent = FileSystem::read($expectedFile); + $expectedFileContent = $expectedFileInfo->getContents(); $this->assertStringMatchesFormat($expectedFileContent, $changedContent, $causedByFixtureMessage); } } - private function createCausedByFixtureMessage(string $fixtureFile): string - { - return (new SmartFileInfo($fixtureFile))->getRelativeFilePathFromCwd(); - } - private function createRectorRepositoryContainer(): void { if (self::$allRectorContainer === null) { diff --git a/src/Testing/PHPUnit/AbstractRunnableRectorTestCase.php b/src/Testing/PHPUnit/AbstractRunnableRectorTestCase.php deleted file mode 100644 index 3710da738960..000000000000 --- a/src/Testing/PHPUnit/AbstractRunnableRectorTestCase.php +++ /dev/null @@ -1,68 +0,0 @@ - refactor in a method - */ - $smartFileInfo = new SmartFileInfo($file); - if (Strings::match($smartFileInfo->getContents(), SplitLine::SPLIT_LINE)) { - [$originalContent, $expectedContent] = Strings::split($smartFileInfo->getContents(), SplitLine::SPLIT_LINE); - } else { - $originalContent = $smartFileInfo->getContents(); - $expectedContent = $originalContent; - } - - $originalInstance = $this->loadClass($originalContent); - if ($originalInstance !== null) { - $expectedInstance = $this->loadClass($expectedContent); - if ($expectedInstance !== null) { - $actual = $originalInstance->run(); - $expected = $expectedInstance->run(); - - $this->assertSame($actual, $expected); - } - } - } - - protected function getTemporaryClassName(): string - { - $testName = (new ReflectionClass(static::class))->getShortName(); - // Todo - pull in Ramsey UUID to generate temporay class names? -// $uuid = Uuid::uuid4()->toString(); - $uuid = md5((string) random_int(0, 100000000)); - $className = $testName . '_' . $uuid; - - return Strings::replace($className, '#[^0-9a-zA-Z]#', '_'); - } - - protected function loadClass(string $classContent): ?RunnableInterface - { - $className = $this->getTemporaryClassName(); - $loadable = Strings::replace($classContent, '#\\s*<\\?php#', ''); - $loadable = Strings::replace($loadable, '#\\s*namespace.*;#', ''); - $loadable = Strings::replace($loadable, '#class\\s+(\\S*)\\s+#', sprintf('class %s ', $className)); - eval($loadable); - if (is_a($className, RunnableInterface::class, true)) { - /** - * @var RunnableInterface - */ - return new $className(); - } - return null; - } -} diff --git a/src/Testing/PHPUnit/FixtureSplitter.php b/src/Testing/PHPUnit/FixtureSplitter.php index 551641da893f..89728b568ff8 100644 --- a/src/Testing/PHPUnit/FixtureSplitter.php +++ b/src/Testing/PHPUnit/FixtureSplitter.php @@ -22,20 +22,13 @@ public function __construct(string $tempPath) } /** - * @return string[] + * @return SmartFileInfo[] */ public function splitContentToOriginalFileAndExpectedFile( SmartFileInfo $smartFileInfo, bool $autoloadTestFixture ): array { - if (Strings::match($smartFileInfo->getContents(), SplitLine::SPLIT_LINE)) { - // original → expected - [$originalContent, $expectedContent] = Strings::split($smartFileInfo->getContents(), SplitLine::SPLIT_LINE); - } else { - // no changes - $originalContent = $smartFileInfo->getContents(); - $expectedContent = $originalContent; - } + [$originalContent, $expectedContent] = $this->resolveBeforeAfterFixtureContent($smartFileInfo); $originalFile = $this->createTemporaryPathWithPrefix($smartFileInfo, 'original'); $expectedFile = $this->createTemporaryPathWithPrefix($smartFileInfo, 'expected'); @@ -48,14 +41,34 @@ public function splitContentToOriginalFileAndExpectedFile( require_once $originalFile; } - return [$originalFile, $expectedFile]; + $originalFileInfo = new SmartFileInfo($originalFile); + $expectedFileInfo = new SmartFileInfo($expectedFile); + + return [$originalFileInfo, $expectedFileInfo]; } - private function createTemporaryPathWithPrefix(SmartFileInfo $smartFileInfo, string $prefix): string + public function createTemporaryPathWithPrefix(SmartFileInfo $smartFileInfo, string $prefix): string { // warning: if this hash is too short, the file can becom "identical"; took me 1 hour to find out - $hash = Strings::substring(md5($smartFileInfo->getRealPath()), 0, 12); + $hash = Strings::substring(md5($smartFileInfo->getRealPath()), 0, 15); return sprintf($this->tempPath . '/%s_%s_%s', $prefix, $hash, $smartFileInfo->getBasename('.inc')); } + + /** + * @return string[] + */ + private function resolveBeforeAfterFixtureContent(SmartFileInfo $smartFileInfo): array + { + if (Strings::match($smartFileInfo->getContents(), SplitLine::SPLIT_LINE)) { + // original → expected + [$originalContent, $expectedContent] = Strings::split($smartFileInfo->getContents(), SplitLine::SPLIT_LINE); + } else { + // no changes + $originalContent = $smartFileInfo->getContents(); + $expectedContent = $originalContent; + } + + return [$originalContent, $expectedContent]; + } } diff --git a/src/Testing/PHPUnit/RunnableRectorTrait.php b/src/Testing/PHPUnit/RunnableRectorTrait.php new file mode 100644 index 000000000000..4946780ed00a --- /dev/null +++ b/src/Testing/PHPUnit/RunnableRectorTrait.php @@ -0,0 +1,61 @@ +createRunnableClass($originalFileInfo); + $expectedInstance = $this->createRunnableClass($expectedFileInfo); + + $actualResult = $originalInstance->run(); + $expectedResult = $expectedInstance->run(); + + $this->assertSame($expectedResult, $actualResult); + } + + private function getTemporaryClassName(): string + { + return 'ClassName_' . Random::generate(20); + } + + private function createRunnableClass(SmartFileInfo $classFileInfo): RunnableInterface + { + $temporaryPath = $this->fixtureSplitter->createTemporaryPathWithPrefix($classFileInfo, 'runnable'); + + $fileContent = $classFileInfo->getContents(); + + // use unique class name for before and for after class, so both can be instantiated + $className = $this->getTemporaryClassName(); + $classFileInfo = Strings::replace($fileContent, '#class\\s+(\\S*)\\s+#', sprintf('class %s ', $className)); + + FileSystem::write($temporaryPath, $classFileInfo); + include_once $temporaryPath; + + $matches = Strings::match($classFileInfo, '#\bnamespace (?.*?);#'); + $namespace = $matches['namespace'] ?? ''; + + $fullyQualifiedClassName = $namespace . '\\' . $className; + + if (! is_a($fullyQualifiedClassName, RunnableInterface::class, true)) { + throw new ShouldNotHappenException(); + } + + return new $fullyQualifiedClassName(); + } +}