add TokensByFilePathStorage

This commit is contained in:
TomasVotruba 2020-06-18 18:11:17 +02:00
parent 919fafd57b
commit 48bbc835b1
12 changed files with 266 additions and 82 deletions

View File

@ -9,8 +9,9 @@ use PhpParser\Lexer;
use PhpParser\Node;
use PhpParser\ParserFactory;
use Rector\Autodiscovery\ValueObject\NodesWithFileDestinationValueObject;
use Rector\Core\Application\FileProcessor;
use Rector\Core\Application\TokensByFilePathStorage;
use Rector\Core\Configuration\Configuration;
use Rector\Core\PhpParser\Parser\Parser;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\Core\PhpParser\Printer\FormatPerservingPrinter;
use Rector\Core\Rector\AbstractRector\AbstractRectorTrait;
@ -41,11 +42,6 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface
*/
private $oldStmts = [];
/**
* @var Parser
*/
private $parser;
/**
* @var Lexer
*/
@ -76,11 +72,20 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface
*/
private $renamedClassesCollector;
/**
* @var TokensByFilePathStorage
*/
private $tokensByFilePathStorage;
/**
* @var FileProcessor
*/
private $fileProcessor;
/**
* @required
*/
public function autowireAbstractFileSystemRector(
Parser $parser,
ParserFactory $parserFactory,
Lexer $lexer,
FormatPerservingPrinter $formatPerservingPrinter,
@ -89,9 +94,10 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface
BetterStandardPrinter $betterStandardPrinter,
ParameterProvider $parameterProvider,
PostFileProcessor $postFileProcessor,
RenamedClassesCollector $renamedClassesCollector
RenamedClassesCollector $renamedClassesCollector,
TokensByFilePathStorage $tokensByFilePathStorage,
FileProcessor $fileProcessor
): void {
$this->parser = $parser;
$this->parserFactory = $parserFactory;
$this->lexer = $lexer;
$this->formatPerservingPrinter = $formatPerservingPrinter;
@ -101,6 +107,8 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface
$this->parameterProvider = $parameterProvider;
$this->postFileProcessor = $postFileProcessor;
$this->renamedClassesCollector = $renamedClassesCollector;
$this->tokensByFilePathStorage = $tokensByFilePathStorage;
$this->fileProcessor = $fileProcessor;
}
protected function addClassRename(string $oldClass, string $newClass): void
@ -113,7 +121,12 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface
*/
protected function parseFileInfoToNodes(SmartFileInfo $smartFileInfo): array
{
$oldStmts = $this->parser->parseFileInfo($smartFileInfo);
if (! $this->tokensByFilePathStorage->hasForRealPath($smartFileInfo->getRealPath())) {
$this->fileProcessor->parseFileInfoToLocalCache($smartFileInfo);
}
[, $oldStmts] = $this->tokensByFilePathStorage->getForRealPath($smartFileInfo->getRealPath());
// needed for format preserving
$this->oldStmts = $oldStmts;
@ -125,7 +138,12 @@ abstract class AbstractFileSystemRector implements FileSystemRectorInterface
*/
protected function parseFileInfoToNodesWithoutScope(SmartFileInfo $smartFileInfo): array
{
$oldStmts = $this->parser->parseFileInfo($smartFileInfo);
if (! $this->tokensByFilePathStorage->hasForRealPath($smartFileInfo->getRealPath())) {
$this->fileProcessor->parseFileInfoToLocalCache($smartFileInfo);
}
[, $oldStmts] = $this->tokensByFilePathStorage->getForRealPath($smartFileInfo->getRealPath());
$this->oldStmts = $oldStmts;
return $oldStmts;

View File

@ -315,3 +315,4 @@ parameters:
- '#Parameter \#1 \$oldMethod of class Rector\\PHPOffice\\ValueObject\\ConditionalSetValue constructor expects string, bool\|int\|string given#'
- '#Parameter \#2 \$scope of method PHPStan\\Analyser\\NodeScopeResolver\:\:processNodes\(\) expects PHPStan\\Analyser\\MutatingScope, PHPStan\\Analyser\\Scope given#'
- '#Parameter \#1 \$object of function get_class expects object, PhpParser\\Node\|null given#'
- '#Cognitive complexity for "Rector\\Core\\Application\\RectorApplication\:\:runOnFileInfos\(\)" is 10, keep it under 9#'

View File

@ -59,13 +59,14 @@ final class FileMover
return null;
}
$currentClassName = $currentNamespace->name->toString() . '\\' . $smartFileInfo->getBasenameWithoutSuffix();
// is already in the right group
if (Strings::endsWith((string) $currentNamespace->name, '\\' . $desiredGroupName)) {
$currentNamespaceName = (string) $currentNamespace->name;
if (Strings::endsWith($currentNamespaceName, '\\' . $desiredGroupName)) {
return null;
}
$currentClassName = $currentNamespaceName . '\\' . $smartFileInfo->getBasenameWithoutSuffix();
// change namespace to new one
$newNamespaceName = $this->createNewNamespaceName($desiredGroupName, $currentNamespace);
$newClassName = $this->createNewClassName($smartFileInfo, $newNamespaceName);
@ -76,11 +77,7 @@ final class FileMover
}
// 1. rename namespace
foreach ($nodes as $node) {
if ($node instanceof Namespace_) {
$node->name = new Name($newNamespaceName);
}
}
$this->renameNamespace($nodes, $newNamespaceName);
// 2. return changed nodes and new file destination
$newFileDestination = $this->fileRelocationResolver->createNewFileDestination(
@ -110,4 +107,19 @@ final class FileMover
{
return $newNamespaceName . '\\' . $smartFileInfo->getBasenameWithoutSuffix();
}
/**
* @param Node[] $nodes
*/
private function renameNamespace(array $nodes, string $newNamespaceName): void
{
foreach ($nodes as $node) {
if (! $node instanceof Namespace_) {
continue;
}
$node->name = new Name($newNamespaceName);
break;
}
}
}

View File

@ -7,11 +7,8 @@ namespace Rector\Autodiscovery\Rector\FileSystem;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Interface_;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeWithClassName;
use Rector\Autodiscovery\FileMover\FileMover;
use Rector\Autodiscovery\ValueObject\NodesWithFileDestinationValueObject;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\FileSystemRector\Rector\AbstractFileSystemRector;
@ -82,15 +79,11 @@ PHP
{
/** @var Interface_|null $interface */
$interface = $this->betterNodeFinder->findFirstInstanceOf($nodes, Interface_::class);
if ($interface === null) {
return;
}
$oldInterfaceName = $this->getName($interface);
if ($oldInterfaceName === null) {
throw new ShouldNotHappenException();
}
if ($this->isNetteMagicGeneratedFactory($interface)) {
return;
}
@ -102,29 +95,14 @@ PHP
return;
}
$newInterfaceName = $this->resolveNewClassLikeName($nodesWithFileDestination);
$this->removeFile($smartFileInfo);
$this->addClassRename($oldInterfaceName, $newInterfaceName);
$this->printNodesWithFileDestination($nodesWithFileDestination);
}
private function resolveNewClassLikeName(
NodesWithFileDestinationValueObject $nodesWithFileDestinationValueObject
): string {
/** @var ClassLike $classLike */
$classLike = $this->betterNodeFinder->findFirstInstanceOf(
$nodesWithFileDestinationValueObject->getNodes(),
ClassLike::class
$this->addClassRename(
$nodesWithFileDestination->getOldClassName(),
$nodesWithFileDestination->getNewClassName()
);
$classLikeName = $this->getName($classLike);
if ($classLikeName === null) {
throw new ShouldNotHappenException();
}
return $classLikeName;
$this->printNodesWithFileDestination($nodesWithFileDestination);
}
/**
@ -138,11 +116,13 @@ PHP
continue;
}
if ($returnType->isSuperTypeOf(new ObjectType('Nette\Application\UI\Control'))) {
$className = $returnType->getClassName();
if (is_a($className, 'Nette\Application\UI\Control', true)) {
return true;
}
if ($returnType->isSuperTypeOf(new ObjectType('Nette\Application\UI\Form'))) {
if (is_a($className, 'Nette\Application\UI\Form', true)) {
return true;
}
}

View File

@ -15,6 +15,7 @@ final class MoveEntitiesToEntityDirectoryRectorTest extends AbstractFileSystemRe
*/
public function test(string $originalFile, string $expectedFileLocation, string $expectedFileContent): void
{
// @todo add extra files too, wehre the calass name will be changed
$this->doTestFile($originalFile);
$this->assertFileExists($expectedFileLocation);

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Rector\Autodiscovery\Tests\Rector\FileSystem\MoveInterfacesToContractNamespaceDirectoryRector\Source;
use Rector\Autodiscovery\Tests\Rector\FileSystem\MoveEntitiesToEntityDirectoryRector\Source\Contract\RandomInterface;
class SomeFactory
{
public function create(): \Rector\Autodiscovery\Tests\Rector\FileSystem\MoveEntitiesToEntityDirectoryRector\Source\Contract\RandomInterface
{
}
}

View File

@ -12,13 +12,28 @@ final class MoveInterfacesToContractNamespaceDirectoryRectorTest extends Abstrac
{
/**
* @dataProvider provideData()
* @param string[] $extraFiles
*/
public function test(string $originalFile, string $expectedFileLocation, string $expectedFileContent): void
{
$this->doTestFile($originalFile);
public function test(
string $originalFile,
string $expectedFileLocation,
string $expectedFileContent,
array $extraFiles = [],
?string $extraExpectedFileLocation = null,
?string $expectedExtraFileContent = null
): void {
$this->doTestFile($originalFile, $extraFiles);
$this->assertFileExists($expectedFileLocation);
$this->assertFileEquals($expectedFileContent, $expectedFileLocation);
if ($extraExpectedFileLocation !== null) {
$this->assertFileExists($extraExpectedFileLocation);
if ($expectedExtraFileContent !== null) {
$this->assertFileEquals($expectedExtraFileContent, $extraExpectedFileLocation);
}
}
}
public function provideData(): Iterator
@ -27,6 +42,10 @@ final class MoveInterfacesToContractNamespaceDirectoryRectorTest extends Abstrac
__DIR__ . '/Source/Entity/RandomInterface.php',
$this->getFixtureTempDirectory() . '/Source/Contract/RandomInterface.php',
__DIR__ . '/Expected/ExpectedRandomInterface.php',
// extra file
[__DIR__ . '/Source/RandomInterfaceUseCase.php'],
$this->getFixtureTempDirectory() . '/Source/RandomInterfaceUseCase.php',
__DIR__ . '/Expected/ExpectedRandomInterfaceUseCase.php',
];
// skip nette control factory

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Rector\Autodiscovery\Tests\Rector\FileSystem\MoveInterfacesToContractNamespaceDirectoryRector\Source;
use Rector\Autodiscovery\Tests\Rector\FileSystem\MoveEntitiesToEntityDirectoryRector\Source\Entity\RandomInterface;
class SomeFactory
{
public function create(): RandomInterface
{
}
}

View File

@ -19,11 +19,6 @@ use Symplify\SmartFileSystem\SmartFileInfo;
final class FileProcessor
{
/**
* @var mixed[][]
*/
private $tokensByFilePath = [];
/**
* @var FormatPerservingPrinter
*/
@ -69,6 +64,11 @@ final class FileProcessor
*/
private $postFileProcessor;
/**
* @var TokensByFilePathStorage
*/
private $tokensByFilePathStorage;
public function __construct(
FormatPerservingPrinter $formatPerservingPrinter,
Parser $parser,
@ -78,7 +78,8 @@ final class FileProcessor
CurrentFileInfoProvider $currentFileInfoProvider,
StubLoader $stubLoader,
AffectedFilesCollector $affectedFilesCollector,
PostFileProcessor $postFileProcessor
PostFileProcessor $postFileProcessor,
TokensByFilePathStorage $tokensByFilePathStorage
) {
$this->formatPerservingPrinter = $formatPerservingPrinter;
$this->parser = $parser;
@ -89,12 +90,12 @@ final class FileProcessor
$this->stubLoader = $stubLoader;
$this->affectedFilesCollector = $affectedFilesCollector;
$this->postFileProcessor = $postFileProcessor;
$this->tokensByFilePathStorage = $tokensByFilePathStorage;
}
public function parseFileInfoToLocalCache(SmartFileInfo $smartFileInfo): void
{
if (isset($this->tokensByFilePath[$smartFileInfo->getRealPath()])) {
// already parsed
if ($this->tokensByFilePathStorage->hasForRealPath($smartFileInfo->getRealPath())) {
return;
}
@ -109,12 +110,14 @@ final class FileProcessor
}
// store tokens by absolute path, so we don't have to print them right now
$this->tokensByFilePath[$smartFileInfo->getRealPath()] = [$newStmts, $oldStmts, $oldTokens];
$this->tokensByFilePathStorage->addForRealPath($smartFileInfo->getRealPath(), $newStmts, $oldStmts, $oldTokens);
}
public function printToFile(SmartFileInfo $smartFileInfo): string
{
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePath[$smartFileInfo->getRealPath()];
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePathStorage->getForRealPath(
$smartFileInfo->getRealPath()
);
return $this->formatPerservingPrinter->printToFile($smartFileInfo, $newStmts, $oldStmts, $oldTokens);
}
@ -125,7 +128,9 @@ final class FileProcessor
{
$this->makeSureFileIsParsed($smartFileInfo);
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePath[$smartFileInfo->getRealPath()];
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePathStorage->getForRealPath(
$smartFileInfo->getRealPath()
);
return $this->formatPerservingPrinter->printToString($newStmts, $oldStmts, $oldTokens);
}
@ -136,7 +141,9 @@ final class FileProcessor
$this->makeSureFileIsParsed($smartFileInfo);
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePath[$smartFileInfo->getRealPath()];
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePathStorage->getForRealPath(
$smartFileInfo->getRealPath()
);
$this->currentFileInfoProvider->setCurrentStmt($newStmts);
@ -144,7 +151,7 @@ final class FileProcessor
$newStmts = $this->postFileProcessor->traverse($newStmts);
// this is needed for new tokens added in "afterTraverse()"
$this->tokensByFilePath[$smartFileInfo->getRealPath()] = [$newStmts, $oldStmts, $oldTokens];
$this->tokensByFilePathStorage->addForRealPath($smartFileInfo->getRealPath(), $newStmts, $oldStmts, $oldTokens);
$this->affectedFilesCollector->removeFromList($smartFileInfo);
while ($otherTouchedFile = $this->affectedFilesCollector->getNext()) {
@ -152,6 +159,24 @@ final class FileProcessor
}
}
public function postFileRefactor(SmartFileInfo $smartFileInfo): void
{
if (! $this->tokensByFilePathStorage->hasForRealPath($smartFileInfo->getRealPath())) {
$this->parseFileInfoToLocalCache($smartFileInfo);
}
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePathStorage->getForRealPath(
$smartFileInfo->getRealPath()
);
$this->currentFileInfoProvider->setCurrentStmt($newStmts);
$newStmts = $this->postFileProcessor->traverse($newStmts);
// this is needed for new tokens added in "afterTraverse()"
$this->tokensByFilePathStorage->addForRealPath($smartFileInfo->getRealPath(), $newStmts, $oldStmts, $oldTokens);
}
/**
* @return Node[][]|mixed[]
*/
@ -161,7 +186,7 @@ final class FileProcessor
$oldTokens = $this->lexer->getTokens();
// needed for \Rector\NodeTypeResolver\PHPStan\Scope\NodeScopeResolver
$this->tokensByFilePath[$smartFileInfo->getRealPath()] = [$oldStmts, $oldStmts, $oldTokens];
$this->tokensByFilePathStorage->addForRealPath($smartFileInfo->getRealPath(), $oldStmts, $oldStmts, $oldTokens);
$newStmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($oldStmts, $smartFileInfo);
@ -170,7 +195,7 @@ final class FileProcessor
private function makeSureFileIsParsed(SmartFileInfo $smartFileInfo): void
{
if (isset($this->tokensByFilePath[$smartFileInfo->getRealPath()])) {
if ($this->tokensByFilePathStorage->hasForRealPath($smartFileInfo->getRealPath())) {
return;
}

View File

@ -121,8 +121,8 @@ final class RectorApplication
}
if (! $this->symfonyStyle->isVerbose() && $this->configuration->showProgressBar()) {
// why 3? one for each cycle, so user sees some activity all the time
$this->symfonyStyle->progressStart($fileCount * 3);
// why 5? one for each cycle, so user sees some activity all the time
$this->symfonyStyle->progressStart($fileCount * 5);
$this->configureStepCount($this->symfonyStyle);
}
@ -130,6 +130,12 @@ final class RectorApplication
// PHPStan has to know about all files!
$this->configurePHPStanNodeScopeResolver($fileInfos);
// active only one rule
if ($this->configuration->getOnlyRector() !== null) {
$onlyRector = $this->configuration->getOnlyRector();
$this->enabledRectorsProvider->addEnabledRector($onlyRector);
}
// 1. parse files to nodes
foreach ($fileInfos as $fileInfo) {
$this->tryCatchWrapper($fileInfo, function (SmartFileInfo $smartFileInfo): void {
@ -137,12 +143,6 @@ final class RectorApplication
}, 'parsing');
}
// active only one rule
if ($this->configuration->getOnlyRector() !== null) {
$onlyRector = $this->configuration->getOnlyRector();
$this->enabledRectorsProvider->addEnabledRector($onlyRector);
}
// 2. change nodes with Rectors
foreach ($fileInfos as $fileInfo) {
$this->tryCatchWrapper($fileInfo, function (SmartFileInfo $smartFileInfo): void {
@ -150,10 +150,24 @@ final class RectorApplication
}, 'refactoring');
}
// 3. print to file or string
// 3. process file system rectors
foreach ($fileInfos as $fileInfo) {
$this->tryCatchWrapper($fileInfo, function (SmartFileInfo $smartFileInfo): void {
$this->processFileInfo($smartFileInfo);
$this->processFileSystemRectors($smartFileInfo);
}, 'refactoring with file system');
}
// 4. apply post rectors
foreach ($fileInfos as $fileInfo) {
$this->tryCatchWrapper($fileInfo, function (SmartFileInfo $smartFileInfo): void {
$this->fileProcessor->postFileRefactor($smartFileInfo);
}, 'post rectors');
}
// 5. print to file or string
foreach ($fileInfos as $fileInfo) {
$this->tryCatchWrapper($fileInfo, function (SmartFileInfo $smartFileInfo): void {
$this->printFileInfo($smartFileInfo);
}, 'printing');
}
@ -222,7 +236,7 @@ final class RectorApplication
}
}
private function processFileInfo(SmartFileInfo $fileInfo): void
private function printFileInfo(SmartFileInfo $fileInfo): void
{
if ($this->removedAndAddedFilesCollector->isFileRemoved($fileInfo)) {
// skip, because this file exists no more
@ -235,8 +249,6 @@ final class RectorApplication
: $this->fileProcessor->printToFile($fileInfo);
$this->errorAndDiffCollector->addFileDiff($fileInfo, $newContent, $oldContent);
$this->fileSystemFileProcessor->processFileInfo($fileInfo);
}
private function advance(SmartFileInfo $smartFileInfo, string $phase): void
@ -248,4 +260,14 @@ final class RectorApplication
$this->symfonyStyle->progressAdvance();
}
}
private function processFileSystemRectors(SmartFileInfo $smartFileInfo): void
{
if ($this->removedAndAddedFilesCollector->isFileRemoved($smartFileInfo)) {
// skip, because this file exists no more
return;
}
$this->fileSystemFileProcessor->processFileInfo($smartFileInfo);
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Application;
use PhpParser\Node;
use PhpParser\Node\Stmt;
final class TokensByFilePathStorage
{
/**
* @todo use value object
* @var Node[][][]|Stmt[][][]
*/
private $tokensByFilePath = [];
/**
* @todo replace with SmartFileInfo $realPath
*
* @param Node[]|Stmt[] $newStmts
* @param Node[]|Stmt[] $oldStmts
* @param Node[]|Stmt[] $oldTokens
*/
public function addForRealPath(string $realPath, array $newStmts, array $oldStmts, array $oldTokens): void
{
$this->tokensByFilePath[$realPath] = [$newStmts, $oldStmts, $oldTokens];
}
/**
* @todo replace with SmartFileInfo $realPath
*/
public function hasForRealPath(string $realPath): bool
{
return isset($this->tokensByFilePath[$realPath]);
}
/**
* @todo replace with SmartFileInfo $realPath
*
* @return Node[][]|Stmt[][]
*/
public function getForRealPath(string $realPath): array
{
return $this->tokensByFilePath[$realPath];
}
}

View File

@ -6,6 +6,7 @@ namespace Rector\Core\Testing\PHPUnit;
use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Rector\Core\Application\FileProcessor;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesProcessor;
use Rector\Core\Configuration\Configuration;
use Rector\Core\HttpKernel\RectorKernel;
@ -27,6 +28,11 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes
*/
private $removedAndAddedFilesProcessor;
/**
* @var FileProcessor
*/
private $fileProcessor;
protected function setUp(): void
{
$this->createContainerWithProvidedRector();
@ -35,6 +41,7 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes
$configuration = self::$container->get(Configuration::class);
$configuration->setIsDryRun(false);
$this->fileProcessor = self::$container->get(FileProcessor::class);
$this->fileSystemFileProcessor = self::$container->get(FileSystemFileProcessor::class);
$this->removedAndAddedFilesProcessor = self::$container->get(RemovedAndAddedFilesProcessor::class);
}
@ -49,13 +56,36 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes
return $temporaryFilePath;
}
protected function doTestFile(string $file): string
/**
* @param string[] $extraFiles
*/
protected function doTestFile(string $file, array $extraFiles = []): string
{
$temporaryFilePath = $this->createTemporaryFilePathFromFilePath($file);
require_once $temporaryFilePath;
$fileInfo = new SmartFileInfo($temporaryFilePath);
$this->fileSystemFileProcessor->processFileInfo($fileInfo);
$filesInfos = [$fileInfo];
foreach ($extraFiles as $extraFile) {
$temporaryExtraFilePath = $this->createTemporaryFilePathFromFilePath($extraFile);
require_once $temporaryExtraFilePath;
$extraFileInfo = new SmartFileInfo($temporaryExtraFilePath);
$this->fileSystemFileProcessor->processFileInfo($extraFileInfo);
$filesInfos[] = $extraFileInfo;
}
foreach ($filesInfos as $fileInfo) {
if (! file_exists($fileInfo->getPathname())) {
continue;
}
$this->fileProcessor->postFileRefactor($fileInfo);
$this->fileProcessor->printToFile($fileInfo);
}
$this->fileSystemFileProcessor->processFileInfo(new SmartFileInfo($temporaryFilePath));
$this->removedAndAddedFilesProcessor->run();
return $temporaryFilePath;
@ -107,6 +137,7 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes
$relativeFilePath = $fileInfo->getRelativeFilePathFromDirectory($testCaseDirectory);
$temporaryFilePath = $this->getFixtureTempDirectory() . '/' . $relativeFilePath;
FileSystem::delete($temporaryFilePath);
FileSystem::copy($file, $temporaryFilePath, true);
return $temporaryFilePath;