Skip to content

Commit

Permalink
[Internals] Merge PhpFileProcessor to FileProcessor (#4977)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Sep 10, 2023
1 parent 0c5ad10 commit 3fcbb53
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 193 deletions.
5 changes: 2 additions & 3 deletions src/Application/ApplicationFileProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Nette\Utils\FileSystem as UtilsFileSystem;
use PHPStan\Collectors\CollectedData;
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\Core\Application\FileProcessor\PhpFileProcessor;
use Rector\Core\Configuration\Option;
use Rector\Core\Configuration\Parameter\SimpleParameterProvider;
use Rector\Core\Provider\CurrentFileProvider;
Expand Down Expand Up @@ -48,7 +47,7 @@ public function __construct(
private readonly CpuCoreCountProvider $cpuCoreCountProvider,
private readonly ChangedFilesDetector $changedFilesDetector,
private readonly CurrentFileProvider $currentFileProvider,
private readonly PhpFileProcessor $phpFileProcessor,
private readonly FileProcessor $fileProcessor,
private readonly ArrayParametersMerger $arrayParametersMerger,
) {
}
Expand Down Expand Up @@ -152,7 +151,7 @@ private function processFile(File $file, Configuration $configuration): FileProc
{
$this->currentFileProvider->setFile($file);

$fileProcessResult = $this->phpFileProcessor->process($file, $configuration);
$fileProcessResult = $this->fileProcessor->processFile($file, $configuration);

if ($fileProcessResult->getSystemErrors() !== []) {
$this->changedFilesDetector->invalidateFile($file->getFilePath());
Expand Down
182 changes: 168 additions & 14 deletions src/Application/FileProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,188 @@

namespace Rector\Core\Application;

use Nette\Utils\Strings;
use PHPStan\AnalysedCodeException;
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\ChangesReporting\ValueObjectFactory\ErrorFactory;
use Rector\ChangesReporting\ValueObjectFactory\FileDiffFactory;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\FileSystem\FilePathHelper;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\PhpParser\NodeTraverser\RectorNodeTraverser;
use Rector\Core\PhpParser\Parser\RectorParser;
use Rector\Core\PhpParser\Printer\FormatPerservingPrinter;
use Rector\Core\ValueObject\Application\File;
use Rector\Core\ValueObject\Configuration;
use Rector\Core\ValueObject\Error\SystemError;
use Rector\Core\ValueObject\FileProcessResult;
use Rector\Core\ValueObject\Reporting\FileDiff;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use Rector\PostRector\Application\PostFileProcessor;
use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment;
use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;

final class FileProcessor
{
/**
* @var string
* @see https://regex101.com/r/xP2MGa/1
*/
private const OPEN_TAG_SPACED_REGEX = '#^(?<open_tag_spaced>[^\S\r\n]+\<\?php)#m';

public function __construct(
private readonly NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator,
private readonly RectorParser $rectorParser,
private readonly FormatPerservingPrinter $formatPerservingPrinter,
private readonly RectorNodeTraverser $rectorNodeTraverser,
private readonly SymfonyStyle $symfonyStyle,
private readonly FileDiffFactory $fileDiffFactory,
private readonly ChangedFilesDetector $changedFilesDetector,
private readonly ErrorFactory $errorFactory,
private readonly FilePathHelper $filePathHelper,
private readonly PostFileProcessor $postFileProcessor,
private readonly RectorParser $rectorParser,
private readonly NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator,
) {
}

public function parseFileInfoToLocalCache(File $file): void
public function processFile(File $file, Configuration $configuration): FileProcessResult
{
// 1. parse files to nodes
$parsingSystemError = $this->parseFileAndDecorateNodes($file);
if ($parsingSystemError instanceof SystemError) {
// we cannot process this file as the parsing and type resolving itself went wrong
return new FileProcessResult([$parsingSystemError], null, []);
}

$fileHasChanged = false;

// 2. change nodes with Rectors
$rectorWithLineChanges = null;
do {
$file->changeHasChanged(false);

$newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts());

// apply post rectors
$postNewStmts = $this->postFileProcessor->traverse($newStmts);

// this is needed for new tokens added in "afterTraverse()"
$file->changeNewStmts($postNewStmts);

// 3. print to file or string
// important to detect if file has changed
$this->printFile($file, $configuration);

$fileHasChangedInCurrentPass = $file->hasChanged();

if ($fileHasChangedInCurrentPass) {
$file->setFileDiff($this->fileDiffFactory->createTempFileDiff($file));
$rectorWithLineChanges = $file->getRectorWithLineChanges();

$fileHasChanged = true;
}
} while ($fileHasChangedInCurrentPass);

// 5. add as cacheable if not changed at all
if (! $fileHasChanged) {
$this->changedFilesDetector->addCachableFile($file->getFilePath());
}

if ($configuration->shouldShowDiffs() && $rectorWithLineChanges !== null) {
$currentFileDiff = $this->fileDiffFactory->createFileDiffWithLineChanges(
$file,
$file->getOriginalFileContent(),
$file->getFileContent(),
$rectorWithLineChanges
);
$file->setFileDiff($currentFileDiff);
}

return new FileProcessResult([], $file->getFileDiff(), []);
}

private function parseFileAndDecorateNodes(File $file): ?SystemError
{
$this->notifyFile($file);

try {
$this->parseFileNodes($file);
} catch (ShouldNotHappenException $shouldNotHappenException) {
throw $shouldNotHappenException;
} catch (AnalysedCodeException $analysedCodeException) {
// inform about missing classes in tests
if (StaticPHPUnitEnvironment::isPHPUnitRun()) {
throw $analysedCodeException;
}

return $this->errorFactory->createAutoloadError($analysedCodeException, $file->getFilePath());
} catch (Throwable $throwable) {
if ($this->symfonyStyle->isVerbose() || StaticPHPUnitEnvironment::isPHPUnitRun()) {
throw $throwable;
}

$relativeFilePath = $this->filePathHelper->relativePath($file->getFilePath());

return new SystemError($throwable->getMessage(), $relativeFilePath, $throwable->getLine());
}

return null;
}

private function printFile(File $file, Configuration $configuration): void
{
// only save to string first, no need to print to file when not needed
$newContent = $this->formatPerservingPrinter->printParsedStmstAndTokensToString($file);

/**
* When no diff applied, the PostRector may still change the content, that's why printing still needed
* On printing, the space may be wiped, these below check compare with original file content used to verify
* that no change actually needed
*/
if (! $file->getFileDiff() instanceof FileDiff && current(
$file->getNewStmts()
) instanceof FileWithoutNamespace) {
/**
* Handle new line or space before <?php or InlineHTML node wiped on print format preserving
* On very first content level
*/
$originalFileContent = $file->getOriginalFileContent();
$ltrimOriginalFileContent = ltrim($originalFileContent);

if ($ltrimOriginalFileContent === $newContent) {
return;
}

$cleanOriginalContent = Strings::replace($ltrimOriginalFileContent, self::OPEN_TAG_SPACED_REGEX, '<?php');
$cleanNewContent = Strings::replace($newContent, self::OPEN_TAG_SPACED_REGEX, '<?php');

/**
* Handle space before <?php wiped on print format preserving
* On inside content level
*/
if ($cleanOriginalContent === $cleanNewContent) {
return;
}
}

if (! $configuration->isDryRun()) {
$this->formatPerservingPrinter->dumpFile($file->getFilePath(), $newContent);
}

$file->changeFileContent($newContent);
}

private function notifyFile(File $file): void
{
if (! $this->symfonyStyle->isVerbose()) {
return;
}

$relativeFilePath = $this->filePathHelper->relativePath($file->getFilePath());
$this->symfonyStyle->writeln($relativeFilePath);
}

private function parseFileNodes(File $file): void
{
// store tokens by absolute path, so we don't have to print them right now
$stmtsAndTokens = $this->rectorParser->parseFileToStmtsAndTokens($file->getFilePath());
Expand All @@ -31,15 +196,4 @@ public function parseFileInfoToLocalCache(File $file): void
$newStmts = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($file, $oldStmts);
$file->hydrateStmtsAndTokens($newStmts, $oldStmts, $oldTokens);
}

public function process(File $file): void
{
$newStmts = $this->rectorNodeTraverser->traverse($file->getNewStmts());

// apply post rectors
$postNewStmts = $this->postFileProcessor->traverse($newStmts);

// this is needed for new tokens added in "afterTraverse()"
$file->changeNewStmts($postNewStmts);
}
}

0 comments on commit 3fcbb53

Please sign in to comment.