Refactor file processors towards universal collector (#6085)

Co-authored-by: kaizen-ci <info@kaizen-ci.org>
This commit is contained in:
Tomas Votruba 2021-04-12 12:34:04 +02:00 committed by GitHub
parent faca7e2763
commit 06f85e4a68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1163 additions and 859 deletions

View File

@ -6,9 +6,10 @@ namespace Rector\Caching\FileSystem;
use PhpParser\Node;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Dependency\DependencyResolver as PHPStanDependencyResolver;
use PHPStan\File\FileHelper;
use Rector\Core\Configuration\Configuration;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
final class DependencyResolver
{
@ -17,24 +18,31 @@ final class DependencyResolver
*/
private $fileHelper;
/**
* @var Configuration
*/
private $configuration;
/**
* @var PHPStanDependencyResolver
*/
private $phpStanDependencyResolver;
/**
* @var NodeScopeResolver
*/
private $nodeScopeResolver;
/**
* @var PrivatesAccessor
*/
private $privatesAccessor;
public function __construct(
Configuration $configuration,
NodeScopeResolver $nodeScopeResolver,
PHPStanDependencyResolver $phpStanDependencyResolver,
FileHelper $fileHelper
FileHelper $fileHelper,
PrivatesAccessor $privatesAccessor
) {
$this->fileHelper = $fileHelper;
$this->configuration = $configuration;
$this->phpStanDependencyResolver = $phpStanDependencyResolver;
$this->nodeScopeResolver = $nodeScopeResolver;
$this->privatesAccessor = $privatesAccessor;
}
/**
@ -42,12 +50,10 @@ final class DependencyResolver
*/
public function resolveDependencies(Node $node, MutatingScope $mutatingScope): array
{
$fileInfos = $this->configuration->getFileInfos();
$analysedFileAbsolutesPaths = [];
foreach ($fileInfos as $fileInfo) {
$analysedFileAbsolutesPaths[] = $fileInfo->getRealPath();
}
$analysedFileAbsolutesPaths = $this->privatesAccessor->getPrivateProperty(
$this->nodeScopeResolver,
'analysedFiles'
);
$dependencyFiles = [];

View File

@ -5,14 +5,11 @@ declare(strict_types=1);
namespace Rector\ChangesReporting\Application;
use PHPStan\AnalysedCodeException;
use Rector\ChangesReporting\Collector\RectorChangeCollector;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesCollector;
use Rector\Core\Differ\DefaultDiffer;
use Rector\Core\Error\ExceptionCorrector;
use Rector\Core\ValueObject\Application\File;
use Rector\Core\ValueObject\Application\RectorError;
use Rector\Core\ValueObject\Reporting\FileDiff;
use Rector\PostRector\Collector\NodesToRemoveCollector;
use Symplify\ConsoleColorDiff\Console\Output\ConsoleDiffer;
use Symplify\SmartFileSystem\SmartFileInfo;
use Throwable;
@ -23,16 +20,6 @@ final class ErrorAndDiffCollector
*/
private $errors = [];
/**
* @var FileDiff[]
*/
private $fileDiffs = [];
/**
* @var RectorChangeCollector
*/
private $rectorChangeCollector;
/**
* @var ExceptionCorrector
*/
@ -48,30 +35,14 @@ final class ErrorAndDiffCollector
*/
private $nodesToRemoveCollector;
/**
* @var ConsoleDiffer
*/
private $consoleDiffer;
/**
* @var DefaultDiffer
*/
private $defaultDiffer;
public function __construct(
ExceptionCorrector $exceptionCorrector,
NodesToRemoveCollector $nodesToRemoveCollector,
RectorChangeCollector $rectorChangeCollector,
RemovedAndAddedFilesCollector $removedAndAddedFilesCollector,
ConsoleDiffer $consoleDiffer,
DefaultDiffer $defaultDiffer
RemovedAndAddedFilesCollector $removedAndAddedFilesCollector
) {
$this->rectorChangeCollector = $rectorChangeCollector;
$this->exceptionCorrector = $exceptionCorrector;
$this->removedAndAddedFilesCollector = $removedAndAddedFilesCollector;
$this->nodesToRemoveCollector = $nodesToRemoveCollector;
$this->consoleDiffer = $consoleDiffer;
$this->defaultDiffer = $defaultDiffer;
}
/**
@ -87,7 +58,7 @@ final class ErrorAndDiffCollector
return $this->removedAndAddedFilesCollector->getAffectedFilesCount();
}
public function getAddFilesCount(): int
public function getAddedFilesCount(): int
{
return $this->removedAndAddedFilesCollector->getAddedFileCount();
}
@ -102,55 +73,10 @@ final class ErrorAndDiffCollector
return $this->nodesToRemoveCollector->getCount();
}
public function addFileDiff(SmartFileInfo $smartFileInfo, string $newContent, string $oldContent): void
{
if ($newContent === $oldContent) {
return;
}
$rectorChanges = $this->rectorChangeCollector->getRectorChangesByFileInfo($smartFileInfo);
// always keep the most recent diff
$fileDiff = new FileDiff(
$smartFileInfo,
$this->defaultDiffer->diff($oldContent, $newContent),
$this->consoleDiffer->diff($oldContent, $newContent),
$rectorChanges
);
$this->fileDiffs[$smartFileInfo->getRealPath()] = $fileDiff;
}
/**
* @return FileDiff[]
*/
public function getFileDiffs(): array
{
return $this->fileDiffs;
}
/**
* @return SmartFileInfo[]
*/
public function getAffectedFileInfos(): array
{
$fileInfos = [];
foreach ($this->fileDiffs as $fileDiff) {
$fileInfos[] = $fileDiff->getFileInfo();
}
return array_unique($fileInfos);
}
public function getFileDiffsCount(): int
{
return count($this->fileDiffs);
}
public function addAutoloadError(AnalysedCodeException $analysedCodeException, SmartFileInfo $fileInfo): void
public function addAutoloadError(AnalysedCodeException $analysedCodeException, File $file): void
{
$message = $this->exceptionCorrector->getAutoloadExceptionMessageAndAddLocation($analysedCodeException);
$this->errors[] = new RectorError($fileInfo, $message);
$this->errors[] = new RectorError($file->getSmartFileInfo(), $message);
}
public function addErrorWithRectorClassMessageAndFileInfo(
@ -171,10 +97,10 @@ final class ErrorAndDiffCollector
}
}
public function hasErrors(SmartFileInfo $phpFileInfo): bool
public function hasSmartFileErrors(File $file): bool
{
foreach ($this->errors as $error) {
if ($error->getFileInfo() === $phpFileInfo) {
if ($error->getFileInfo() === $file->getSmartFileInfo()) {
return true;
}
}

View File

@ -4,21 +4,22 @@ declare(strict_types=1);
namespace Rector\ChangesReporting\Collector;
use Symplify\SmartFileSystem\SmartFileInfo;
use Rector\Core\ValueObject\Application\File;
final class AffectedFilesCollector
{
/**
* @var SmartFileInfo[]
* @var File[]
*/
private $affectedFiles = [];
public function addFile(SmartFileInfo $fileInfo): void
public function addFile(File $file): void
{
$this->affectedFiles[$fileInfo->getRealPath()] = $fileInfo;
$fileInfo = $file->getSmartFileInfo();
$this->affectedFiles[$fileInfo->getRealPath()] = $file;
}
public function getNext(): ?SmartFileInfo
public function getNext(): ?File
{
if ($this->affectedFiles !== []) {
return current($this->affectedFiles);
@ -26,8 +27,9 @@ final class AffectedFilesCollector
return null;
}
public function removeFromList(SmartFileInfo $fileInfo): void
public function removeFromList(File $file): void
{
$fileInfo = $file->getSmartFileInfo();
unset($this->affectedFiles[$fileInfo->getRealPath()]);
}
}

View File

@ -4,11 +4,11 @@ declare(strict_types=1);
namespace Rector\ChangesReporting\Contract\Output;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\Core\ValueObject\ProcessResult;
interface OutputFormatterInterface
{
public function getName(): string;
public function report(ErrorAndDiffCollector $errorAndDiffCollector): void;
public function report(ProcessResult $processResult): void;
}

View File

@ -6,11 +6,11 @@ namespace Rector\ChangesReporting\Output;
use Nette\Utils\Strings;
use Rector\ChangesReporting\Annotation\RectorsChangelogResolver;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\ChangesReporting\Contract\Output\OutputFormatterInterface;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\Application\RectorError;
use Rector\Core\ValueObject\ProcessResult;
use Rector\Core\ValueObject\Reporting\FileDiff;
use Symfony\Component\Console\Style\SymfonyStyle;
@ -52,7 +52,7 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
$this->rectorsChangelogResolver = $rectorsChangelogResolver;
}
public function report(ErrorAndDiffCollector $errorAndDiffCollector): void
public function report(ProcessResult $processResult): void
{
if ($this->configuration->getOutputFile()) {
$message = sprintf(
@ -65,17 +65,17 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
}
if ($this->configuration->shouldShowDiffs()) {
$this->reportFileDiffs($errorAndDiffCollector->getFileDiffs());
$this->reportFileDiffs($processResult->getFileDiffs());
}
$this->reportErrors($errorAndDiffCollector->getErrors());
$this->reportRemovedFilesAndNodes($errorAndDiffCollector);
$this->reportErrors($processResult->getErrors());
$this->reportRemovedFilesAndNodes($processResult);
if ($errorAndDiffCollector->getErrors() !== []) {
if ($processResult->getErrors() !== []) {
return;
}
$message = $this->createSuccessMessage($errorAndDiffCollector);
$message = $this->createSuccessMessage($processResult);
$this->symfonyStyle->success($message);
}
@ -145,19 +145,19 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
}
}
private function reportRemovedFilesAndNodes(ErrorAndDiffCollector $errorAndDiffCollector): void
private function reportRemovedFilesAndNodes(ProcessResult $processResult): void
{
if ($errorAndDiffCollector->getAddFilesCount() !== 0) {
$message = sprintf('%d files were added', $errorAndDiffCollector->getAddFilesCount());
if ($processResult->getAddedFilesCount() !== 0) {
$message = sprintf('%d files were added', $processResult->getAddedFilesCount());
$this->symfonyStyle->note($message);
}
if ($errorAndDiffCollector->getRemovedFilesCount() !== 0) {
$message = sprintf('%d files were removed', $errorAndDiffCollector->getRemovedFilesCount());
if ($processResult->getRemovedFilesCount() !== 0) {
$message = sprintf('%d files were removed', $processResult->getRemovedFilesCount());
$this->symfonyStyle->note($message);
}
$this->reportRemovedNodes($errorAndDiffCollector);
$this->reportRemovedNodes($processResult);
}
private function normalizePathsToRelativeWithLine(string $errorMessage): string
@ -167,20 +167,19 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
return Strings::replace($errorMessage, self::ON_LINE_REGEX, ':');
}
private function reportRemovedNodes(ErrorAndDiffCollector $errorAndDiffCollector): void
private function reportRemovedNodes(ProcessResult $processResult): void
{
if ($errorAndDiffCollector->getRemovedNodeCount() === 0) {
if ($processResult->getRemovedNodeCount() === 0) {
return;
}
$message = sprintf('%d nodes were removed', $errorAndDiffCollector->getRemovedNodeCount());
$message = sprintf('%d nodes were removed', $processResult->getRemovedNodeCount());
$this->symfonyStyle->warning($message);
}
private function createSuccessMessage(ErrorAndDiffCollector $errorAndDiffCollector): string
private function createSuccessMessage(ProcessResult $processResult): string
{
$changeCount = $errorAndDiffCollector->getFileDiffsCount()
+ $errorAndDiffCollector->getRemovedAndAddedFilesCount();
$changeCount = count($processResult->getFileDiffs()) + $processResult->getRemovedAndAddedFilesCount();
if ($changeCount === 0) {
return 'Rector is done!';
@ -203,7 +202,7 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
$rectorsChangelogsLines = [];
foreach ($rectorsChangelogs as $rectorClass => $changelog) {
$rectorsChangelogsLines[] = $changelog === null ? $rectorClass : $rectorClass . ' ' . $changelog;
$rectorsChangelogsLines[] = $changelog === null ? $rectorClass : $rectorClass . ' (' . $changelog . ')';
}
return $rectorsChangelogsLines;

View File

@ -6,9 +6,9 @@ namespace Rector\ChangesReporting\Output;
use Nette\Utils\Json;
use Rector\ChangesReporting\Annotation\RectorsChangelogResolver;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\ChangesReporting\Contract\Output\OutputFormatterInterface;
use Rector\Core\Configuration\Configuration;
use Rector\Core\ValueObject\ProcessResult;
use Symplify\SmartFileSystem\SmartFileSystem;
final class JsonOutputFormatter implements OutputFormatterInterface
@ -48,7 +48,7 @@ final class JsonOutputFormatter implements OutputFormatterInterface
return self::NAME;
}
public function report(ErrorAndDiffCollector $errorAndDiffCollector): void
public function report(ProcessResult $processResult): void
{
$errorsArray = [
'meta' => [
@ -56,13 +56,13 @@ final class JsonOutputFormatter implements OutputFormatterInterface
'config' => $this->configuration->getMainConfigFilePath(),
],
'totals' => [
'changed_files' => $errorAndDiffCollector->getFileDiffsCount(),
'removed_and_added_files_count' => $errorAndDiffCollector->getRemovedAndAddedFilesCount(),
'removed_node_count' => $errorAndDiffCollector->getRemovedNodeCount(),
'changed_files' => count($processResult->getFileDiffs()),
'removed_and_added_files_count' => $processResult->getRemovedAndAddedFilesCount(),
'removed_node_count' => $processResult->getRemovedNodeCount(),
],
];
$fileDiffs = $errorAndDiffCollector->getFileDiffs();
$fileDiffs = $processResult->getFileDiffs();
ksort($fileDiffs);
foreach ($fileDiffs as $fileDiff) {
$relativeFilePath = $fileDiff->getRelativeFilePath();
@ -80,7 +80,7 @@ final class JsonOutputFormatter implements OutputFormatterInterface
$errorsArray['changed_files'][] = $relativeFilePath;
}
$errors = $errorAndDiffCollector->getErrors();
$errors = $processResult->getErrors();
$errorsArray['totals']['errors'] = count($errors);
$errorsData = $this->createErrorsData($errors);

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Rector\ChangesReporting\ValueObjectFactory;
use Rector\ChangesReporting\Collector\RectorChangeCollector;
use Rector\Core\Differ\DefaultDiffer;
use Rector\Core\ValueObject\Application\File;
use Rector\Core\ValueObject\Reporting\FileDiff;
use Symplify\ConsoleColorDiff\Console\Output\ConsoleDiffer;
final class FileDiffFactory
{
/**
* @var RectorChangeCollector
*/
private $rectorChangeCollector;
/**
* @var DefaultDiffer
*/
private $defaultDiffer;
/**
* @var ConsoleDiffer
*/
private $consoleDiffer;
public function __construct(
RectorChangeCollector $rectorChangeCollector,
DefaultDiffer $defaultDiffer,
ConsoleDiffer $consoleDiffer
) {
$this->rectorChangeCollector = $rectorChangeCollector;
$this->defaultDiffer = $defaultDiffer;
$this->consoleDiffer = $consoleDiffer;
}
public function createFileDiff(File $file, string $oldContent, string $newContent): FileDiff
{
$smartFileInfo = $file->getSmartFileInfo();
$rectorChanges = $this->rectorChangeCollector->getRectorChangesByFileInfo($smartFileInfo);
// always keep the most recent diff
return new FileDiff(
$smartFileInfo,
$this->defaultDiffer->diff($oldContent, $newContent),
$this->consoleDiffer->diff($oldContent, $newContent),
$rectorChanges
);
}
}

View File

@ -95,11 +95,7 @@ final class NodeRemover
*/
public function removeParam(ClassMethod $classMethod, $keyOrParam): void
{
if ($keyOrParam instanceof Param) {
$key = $keyOrParam->getAttribute(AttributeKey::PARAMETER_POSITION);
} else {
$key = $keyOrParam;
}
$key = $keyOrParam instanceof Param ? $keyOrParam->getAttribute(AttributeKey::PARAMETER_POSITION) : $keyOrParam;
if ($classMethod->params === null) {
throw new ShouldNotHappenException();

View File

@ -92,7 +92,7 @@ final class NodeScopeAndMetadataDecorator
* @param Node[] $nodes
* @return Node[]
*/
public function decorateNodesFromFile(array $nodes, SmartFileInfo $smartFileInfo, bool $needsScope = false): array
public function decorateNodesFromFile(array $nodes, SmartFileInfo $smartFileInfo): array
{
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor(new NameResolver(null, [

View File

@ -78,15 +78,20 @@ final class TypeHasher
return $booleanType->describe(VerbosityLevel::precise());
}
$normalizedUnionType = clone $sortedUnionType;
// change alias to non-alias
$sortedUnionType = TypeTraverser::map($sortedUnionType, function (Type $type, callable $callable): Type {
if (! $type instanceof AliasedObjectType) {
return $callable($type);
$normalizedUnionType = TypeTraverser::map(
$normalizedUnionType,
function (Type $type, callable $callable): Type {
if (! $type instanceof AliasedObjectType) {
return $callable($type);
}
return new FullyQualifiedObjectType($type->getFullyQualifiedClass());
}
);
return new FullyQualifiedObjectType($type->getFullyQualifiedClass());
});
return $sortedUnionType->describe(VerbosityLevel::cache());
return $normalizedUnionType->describe(VerbosityLevel::cache());
}
}

View File

@ -21,7 +21,6 @@ use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use PHPStan\Type\VoidType;
use Rector\BetterPhpDocParser\ValueObject\Type\BracketsAwareUnionTypeNode;
use Rector\CodeQuality\Tests\Rector\If_\ExplicitBoolCompareRector\Fixture\Nullable;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Php\PhpVersionProvider;
use Rector\Core\Rector\AbstractRector;

View File

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\PhpAttribute\ValueObject;
final class TagName
{
/**
* @var string
*/
public const API = 'api';
}

View File

@ -17,6 +17,7 @@ use Rector\ChangesReporting\Collector\AffectedFilesCollector;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\ValueObject\Application\File;
use Rector\NodeRemoval\BreakingRemovalGuard;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PostRector\Contract\Collector\NodeCollectorInterface;
@ -82,7 +83,7 @@ final class NodesToRemoveCollector implements NodeCollectorInterface
/** @var SmartFileInfo|null $fileInfo */
$fileInfo = $node->getAttribute(AttributeKey::FILE_INFO);
if ($fileInfo !== null) {
$this->affectedFilesCollector->addFile($fileInfo);
$this->affectedFilesCollector->addFile(new File($fileInfo, $fileInfo->getContents()));
}
/** @var Stmt $node */

View File

@ -5,25 +5,24 @@ declare(strict_types=1);
namespace Rector\Testing\PHPUnit;
use Iterator;
use Nette\Utils\Json;
use Nette\Utils\Strings;
use PHPStan\Analyser\NodeScopeResolver;
use PHPUnit\Framework\ExpectationFailedException;
use Psr\Container\ContainerInterface;
use Rector\Composer\Modifier\ComposerModifier;
use Rector\Core\Application\ApplicationFileProcessor;
use Rector\Core\Application\FileProcessor;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesCollector;
use Rector\Core\Bootstrap\RectorConfigsResolver;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Configuration\Option;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\HttpKernel\RectorKernel;
use Rector\Core\NonPhpFile\NonPhpFileProcessor;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\Core\ValueObject\StaticNonPhpFileSuffixes;
use Rector\Core\ValueObject\Application\File;
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
use Rector\Testing\Contract\RectorTestInterface;
use Rector\Testing\PHPUnit\Behavior\MovingFilesTrait;
use Symplify\ComposerJsonManipulator\ComposerJsonFactory;
use Symplify\EasyTesting\DataProvider\StaticFixtureFinder;
use Symplify\EasyTesting\DataProvider\StaticFixtureUpdater;
use Symplify\EasyTesting\StaticFixtureSplitter;
@ -76,14 +75,9 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase implements
private $dynamicSourceLocatorProvider;
/**
* @var ComposerJsonFactory
* @var ApplicationFileProcessor
*/
private $composerJsonFactory;
/**
* @var ComposerModifier
*/
private $composerModifier;
private $applicationFileProcessor;
protected function setUp(): void
{
@ -99,15 +93,17 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase implements
$this->fileProcessor = $this->getService(FileProcessor::class);
$this->nonPhpFileProcessor = $this->getService(NonPhpFileProcessor::class);
$this->applicationFileProcessor = $this->getService(ApplicationFileProcessor::class);
$this->parameterProvider = $this->getService(ParameterProvider::class);
$this->betterStandardPrinter = $this->getService(BetterStandardPrinter::class);
$this->dynamicSourceLocatorProvider = $this->getService(DynamicSourceLocatorProvider::class);
$this->composerJsonFactory = $this->getService(ComposerJsonFactory::class);
$this->composerModifier = $this->getService(ComposerModifier::class);
$this->removedAndAddedFilesCollector = $this->getService(RemovedAndAddedFilesCollector::class);
$this->removedAndAddedFilesCollector->reset();
/** @var Configuration $configuration */
$configuration = $this->getService(Configuration::class);
$configuration->setIsDryRun(true);
}
public function provideConfigFilePath(): string
@ -131,29 +127,11 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase implements
);
$inputFileInfo = $inputFileInfoAndExpectedFileInfo->getInputFileInfo();
if ($inputFileInfo->getSuffix() === 'json') {
$inputFileInfoAndExpected = StaticFixtureSplitter::splitFileInfoToLocalInputAndExpected($fixtureFileInfo);
$composerJson = $this->composerJsonFactory->createFromFileInfo(
$inputFileInfoAndExpected->getInputFileInfo()
);
$this->composerModifier->modify($composerJson);
$expectedFileInfo = $inputFileInfoAndExpectedFileInfo->getExpectedFileInfo();
$this->doTestFileMatchesExpectedContent($inputFileInfo, $expectedFileInfo, $fixtureFileInfo);
$changedComposerJson = Json::encode($composerJson->getJsonArray(), Json::PRETTY);
$this->assertJsonStringEqualsJsonString($inputFileInfoAndExpected->getExpected(), $changedComposerJson);
} else {
// needed for PHPStan, because the analyzed file is just created in /temp - need for trait and similar deps
/** @var NodeScopeResolver $nodeScopeResolver */
$nodeScopeResolver = $this->getService(NodeScopeResolver::class);
$nodeScopeResolver->setAnalysedFiles([$inputFileInfo->getRealPath()]);
$this->dynamicSourceLocatorProvider->setFileInfo($inputFileInfo);
$expectedFileInfo = $inputFileInfoAndExpectedFileInfo->getExpectedFileInfo();
$this->doTestFileMatchesExpectedContent($inputFileInfo, $expectedFileInfo, $fixtureFileInfo);
$this->originalTempFileInfo = $inputFileInfo;
}
$this->originalTempFileInfo = $inputFileInfo;
}
protected function doTestExtraFile(string $expectedExtraFileName, string $expectedExtraContentFilePath): void
@ -206,6 +184,11 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase implements
$changedContent = $this->processFileInfo($originalFileInfo);
// file is removed, we cannot compare it
if ($this->removedAndAddedFilesCollector->isFileRemoved($originalFileInfo)) {
return;
}
$relativeFilePathFromCwd = $fixtureFileInfo->getRelativeFilePathFromCwd();
try {
@ -227,26 +210,18 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase implements
return Strings::replace($string, '#\r\n|\r|\n#', "\n");
}
private function processFileInfo(SmartFileInfo $originalFileInfo): string
private function processFileInfo(SmartFileInfo $fileInfo): string
{
if (! Strings::endsWith($originalFileInfo->getFilename(), '.blade.php') && in_array(
$originalFileInfo->getSuffix(),
['php', 'phpt'],
true
)) {
$this->fileProcessor->refactor($originalFileInfo);
$this->fileProcessor->postFileRefactor($originalFileInfo);
$this->dynamicSourceLocatorProvider->setFileInfo($fileInfo);
// mimic post-rectors
$changedContent = $this->fileProcessor->printToString($originalFileInfo);
} elseif (Strings::match($originalFileInfo->getFilename(), StaticNonPhpFileSuffixes::getSuffixRegexPattern())) {
$nonPhpFileChange = $this->nonPhpFileProcessor->process($originalFileInfo);
// needed for PHPStan, because the analyzed file is just created in /temp - need for trait and similar deps
/** @var NodeScopeResolver $nodeScopeResolver */
$nodeScopeResolver = $this->getService(NodeScopeResolver::class);
$nodeScopeResolver->setAnalysedFiles([$fileInfo->getRealPath()]);
$changedContent = $nonPhpFileChange !== null ? $nonPhpFileChange->getNewContent() : '';
} else {
$message = sprintf('Suffix "%s" is not supported yet', $originalFileInfo->getSuffix());
throw new ShouldNotHappenException($message);
}
return $changedContent;
$file = new File($fileInfo, $fileInfo->getContents());
$this->applicationFileProcessor->run([$file]);
return $file->getFileContent();
}
}

View File

@ -443,7 +443,6 @@ parameters:
-
message: '#Instead of "(.*?)" use ReflectionProvider service (.*?) for static reflection to work#'
paths:
- src/Application/RectorApplication.php
- src/Console/Command/ProcessCommand.php
# mimics original doctrine/annotations parser, improve later when finished
@ -509,3 +508,12 @@ parameters:
- '#Cognitive complexity for "Rector\\PHPStanStaticTypeMapper\\TypeMapper\\UnionTypeMapper\:\:mapToPhpParserNode\(\)" is 10, keep it under 9#'
- '#Method Rector\\NodeNameResolver\\NodeNameResolver\:\:matchRectorBacktraceCall\(\) return type has no value type specified in iterable type array#'
-
message: '#Do not inherit from abstract class, better use composition#'
paths:
- src/PhpParser/Parser/FunctionLikeParser.php
-
message: '#Unreachable statement \- code above always terminates#'
paths:
- src/Application/FileProcessor/PhpFileProcessor.php

View File

@ -7,6 +7,5 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(FormControlToControllerAndFormTypeRector::class);
};

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Rector\Tests\PSR4\Rector\Namespace_\MultipleClassFileToPsr4ClassesRector;
use Iterator;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesCollector;
use Rector\FileSystemRector\ValueObject\AddedFileWithContent;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
@ -19,10 +18,6 @@ final class MultipleClassFileToPsr4ClassesRectorTest extends AbstractRectorTestC
*/
public function test(SmartFileInfo $originalFileInfo, array $expectedFilePathsWithContents): void
{
/** @var RemovedAndAddedFilesCollector $removedAndAddedFilesCollector */
$removedAndAddedFilesCollector = $this->getService(RemovedAndAddedFilesCollector::class);
$removedAndAddedFilesCollector->reset();
$this->doTestFileInfo($originalFileInfo);
$this->assertFilesWereAdded($expectedFilePathsWithContents);
}

View File

@ -7,6 +7,5 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(MultipleClassFileToPsr4ClassesRector::class);
};

View File

@ -7,6 +7,5 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(PrivatizeLocalPropertyToPrivatePropertyRector::class);
};

View File

@ -2,11 +2,12 @@
declare(strict_types=1);
namespace Rector\Composer\Processor;
namespace Rector\Composer\Application\FileProcessor;
use Rector\Composer\Modifier\ComposerModifier;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\ValueObject\NonPhpFile\NonPhpFileChange;
use Rector\Core\ValueObject\Application\File;
use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment;
use Symplify\ComposerJsonManipulator\ComposerJsonFactory;
use Symplify\ComposerJsonManipulator\Printer\ComposerJsonPrinter;
use Symplify\SmartFileSystem\SmartFileInfo;
@ -38,31 +39,25 @@ final class ComposerFileProcessor implements FileProcessorInterface
$this->composerModifier = $composerModifier;
}
public function process(SmartFileInfo $smartFileInfo): ?NonPhpFileChange
/**
* @param File[] $files
*/
public function process(array $files): void
{
// to avoid modification of file
if (! $this->composerModifier->enabled()) {
return null;
foreach ($files as $file) {
$this->processFile($file);
}
$composerJson = $this->composerJsonFactory->createFromFileInfo($smartFileInfo);
$oldComposerJson = clone $composerJson;
$this->composerModifier->modify($composerJson);
// nothing has changed
if ($oldComposerJson->getJsonArray() === $composerJson->getJsonArray()) {
return null;
}
$oldContent = $this->composerJsonPrinter->printToString($oldComposerJson);
$newContent = $this->composerJsonPrinter->printToString($composerJson);
return new NonPhpFileChange($oldContent, $newContent);
}
public function supports(SmartFileInfo $smartFileInfo): bool
public function supports(File $file): bool
{
return $smartFileInfo->getRealPath() === getcwd() . '/composer.json';
$fileInfo = $file->getSmartFileInfo();
if ($this->isJsonInTests($fileInfo)) {
return true;
}
return $fileInfo->getRealPath() === getcwd() . '/composer.json';
}
/**
@ -72,4 +67,34 @@ final class ComposerFileProcessor implements FileProcessorInterface
{
return ['json'];
}
private function processFile(File $file): void
{
// to avoid modification of file
if (! $this->composerModifier->enabled()) {
return;
}
$smartFileInfo = $file->getSmartFileInfo();
$composerJson = $this->composerJsonFactory->createFromFileInfo($smartFileInfo);
$oldComposerJson = clone $composerJson;
$this->composerModifier->modify($composerJson);
// nothing has changed
if ($oldComposerJson->getJsonArray() === $composerJson->getJsonArray()) {
return;
}
$changeFileContent = $this->composerJsonPrinter->printToString($composerJson);
$file->changeFileContent($changeFileContent);
}
private function isJsonInTests(SmartFileInfo $fileInfo): bool
{
if (! StaticPHPUnitEnvironment::isPHPUnitRun()) {
return false;
}
return $fileInfo->hasSuffixes(['json']);
}
}

View File

@ -8,7 +8,7 @@ use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\PhpParser\Parser\FunctionLikeParser;
use Rector\Core\Reflection\FunctionLikeReflectionParser;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\NodeTypeResolver;
@ -30,20 +30,20 @@ final class MethodCallToClassMethodParser
private $reflectionProvider;
/**
* @var FunctionLikeParser
* @var FunctionLikeReflectionParser
*/
private $functionLikeParser;
private $functionLikeReflectionParser;
public function __construct(
NodeTypeResolver $nodeTypeResolver,
NodeNameResolver $nodeNameResolver,
ReflectionProvider $reflectionProvider,
FunctionLikeParser $functionLikeParser
FunctionLikeReflectionParser $functionLikeReflectionParser
) {
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nodeNameResolver = $nodeNameResolver;
$this->reflectionProvider = $reflectionProvider;
$this->functionLikeParser = $functionLikeParser;
$this->functionLikeReflectionParser = $functionLikeReflectionParser;
}
public function parseMethodCall(MethodCall $methodCall): ?ClassMethod
@ -61,6 +61,6 @@ final class MethodCallToClassMethodParser
$methodReflection = $callerClassReflection->getNativeMethod($methodName);
return $this->functionLikeParser->parseMethodReflection($methodReflection);
return $this->functionLikeReflectionParser->parseMethodReflection($methodReflection);
}
}

View File

@ -73,6 +73,10 @@ final class RemoveExtraParametersRector extends AbstractRector
$maximumAllowedParameterCount = $this->resolveMaximumAllowedParameterCount($functionLikeReflection);
$numberOfArguments = count($node->args);
if ($numberOfArguments <= $maximumAllowedParameterCount) {
return null;
}
for ($i = $maximumAllowedParameterCount; $i <= $numberOfArguments; ++$i) {
unset($node->args[$i]);
}

View File

@ -33,9 +33,6 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @changelog https://wiki.php.net/rfc/scalar_type_hints_v5
* @changelog https://github.com/nikic/TypeUtil
* @changelog https://github.com/nette/type-fixer
* @changelog https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/3258
*
* @see \Rector\Tests\TypeDeclaration\Rector\FunctionLike\ReturnTypeDeclarationRector\ReturnTypeDeclarationRectorTest
*/
@ -266,7 +263,6 @@ CODE_SAMPLE
}
$isSubtype = $this->phpParserTypeAnalyzer->isCovariantSubtypeOf($inferredReturnNode, $functionLike->returnType);
if ($this->isAtLeastPhpVersion(PhpVersionFeature::COVARIANT_RETURN) && $isSubtype) {
$functionLike->returnType = $inferredReturnNode;
return;

View File

@ -49,26 +49,11 @@ final class ActiveRectorsProvider
*/
public function provide(): array
{
return $this->filterOutInternalRectorsAndSort($this->rectors);
}
sort($this->rectors);
/**
* @param RectorInterface[] $rectors
* @return RectorInterface[]
*/
private function filterOutInternalRectorsAndSort(array $rectors): array
{
sort($rectors);
$rectors = array_filter($rectors, function (RectorInterface $rector): bool {
return array_filter($this->rectors, function (RectorInterface $rector): bool {
// skip as internal and always run
return ! $rector instanceof PostRectorInterface;
});
usort($rectors, function (RectorInterface $firstRector, RectorInterface $secondRector): int {
return get_class($firstRector) <=> get_class($secondRector);
});
return $rectors;
}
}

View File

@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Application;
use Rector\Core\Application\FileDecorator\FileDiffFileDecorator;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\ValueObject\Application\File;
use Symplify\SmartFileSystem\SmartFileSystem;
final class ApplicationFileProcessor
{
/**
* @var FileProcessorInterface[]
*/
private $fileProcessors = [];
/**
* @var SmartFileSystem
*/
private $smartFileSystem;
/**
* @var Configuration
*/
private $configuration;
/**
* @var FileDiffFileDecorator
*/
private $fileDiffFileDecorator;
/**
* @param FileProcessorInterface[] $fileProcessors
*/
public function __construct(
Configuration $configuration,
SmartFileSystem $smartFileSystem,
FileDiffFileDecorator $fileDiffFileDecorator,
array $fileProcessors = []
) {
$this->fileProcessors = $fileProcessors;
$this->smartFileSystem = $smartFileSystem;
$this->configuration = $configuration;
$this->fileDiffFileDecorator = $fileDiffFileDecorator;
}
/**
* @param File[] $files
*/
public function run(array $files): void
{
$this->processFiles($files);
$this->fileDiffFileDecorator->decorate($files);
$this->printFiles($files);
}
/**
* @param File[] $files
*/
private function processFiles(array $files): void
{
foreach ($this->fileProcessors as $fileProcessor) {
$supportedFiles = array_filter($files, function (File $file) use ($fileProcessor): bool {
return $fileProcessor->supports($file);
});
$fileProcessor->process($supportedFiles);
}
}
/**
* @param File[] $files
*/
private function printFiles(array $files): void
{
if ($this->configuration->isDryRun()) {
return;
}
foreach ($files as $file) {
if (! $file->hasChanged()) {
continue;
}
$this->printFile($file);
}
}
private function printFile(File $file): void
{
$smartFileInfo = $file->getSmartFileInfo();
$this->smartFileSystem->dumpFile($smartFileInfo->getPathname(), $file->getFileContent());
$this->smartFileSystem->chmod($smartFileInfo->getRealPath(), $smartFileInfo->getPerms());
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Application\FileDecorator;
use Rector\ChangesReporting\ValueObjectFactory\FileDiffFactory;
use Rector\Core\ValueObject\Application\File;
final class FileDiffFileDecorator
{
/**
* @var FileDiffFactory
*/
private $fileDiffFactory;
public function __construct(FileDiffFactory $fileDiffFactory)
{
$this->fileDiffFactory = $fileDiffFactory;
}
/**
* @param File[] $files
*/
public function decorate(array $files): void
{
foreach ($files as $file) {
if (! $file->hasChanged()) {
continue;
}
$fileDiff = $this->fileDiffFactory->createFileDiff(
$file,
$file->getOriginalFileContent(),
$file->getFileContent()
);
$file->setFileDiff($fileDiff);
}
}
}

View File

@ -10,6 +10,7 @@ use Rector\Core\PhpParser\Node\CustomNode\FileNode;
use Rector\Core\PhpParser\NodeTraverser\RectorNodeTraverser;
use Rector\Core\PhpParser\Parser\Parser;
use Rector\Core\PhpParser\Printer\FormatPerservingPrinter;
use Rector\Core\ValueObject\Application\File;
use Rector\Core\ValueObject\Application\ParsedStmtsAndTokens;
use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
@ -85,8 +86,9 @@ final class FileProcessor
$this->tokensByFilePathStorage = $tokensByFilePathStorage;
}
public function parseFileInfoToLocalCache(SmartFileInfo $smartFileInfo): void
public function parseFileInfoToLocalCache(File $file): void
{
$smartFileInfo = $file->getSmartFileInfo();
if ($this->tokensByFilePathStorage->hasForFileInfo($smartFileInfo)) {
return;
}
@ -113,10 +115,11 @@ final class FileProcessor
return $this->formatPerservingPrinter->printParsedStmstAndTokensToString($parsedStmtsAndTokens);
}
public function refactor(SmartFileInfo $smartFileInfo): void
public function refactor(File $file): void
{
$this->parseFileInfoToLocalCache($smartFileInfo);
$this->parseFileInfoToLocalCache($file);
$smartFileInfo = $file->getSmartFileInfo();
$parsedStmtsAndTokens = $this->tokensByFilePathStorage->getForFileInfo($smartFileInfo);
$this->currentFileInfoProvider->setCurrentStmts($parsedStmtsAndTokens->getNewStmts());
@ -130,16 +133,18 @@ final class FileProcessor
// this is needed for new tokens added in "afterTraverse()"
$parsedStmtsAndTokens->updateNewStmts($newStmts);
$this->affectedFilesCollector->removeFromList($smartFileInfo);
$this->affectedFilesCollector->removeFromList($file);
while ($otherTouchedFile = $this->affectedFilesCollector->getNext()) {
$this->refactor($otherTouchedFile);
}
}
public function postFileRefactor(SmartFileInfo $smartFileInfo): void
public function postFileRefactor(File $file): void
{
$smartFileInfo = $file->getSmartFileInfo();
if (! $this->tokensByFilePathStorage->hasForFileInfo($smartFileInfo)) {
$this->parseFileInfoToLocalCache($smartFileInfo);
$this->parseFileInfoToLocalCache($file);
}
$parsedStmtsAndTokens = $this->tokensByFilePathStorage->getForFileInfo($smartFileInfo);

View File

@ -2,34 +2,24 @@
declare(strict_types=1);
namespace Rector\Core\Application;
namespace Rector\Core\Application\FileProcessor;
use PHPStan\AnalysedCodeException;
use PHPStan\Analyser\NodeScopeResolver;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\Core\Application\FileDecorator\FileDiffFileDecorator;
use Rector\Core\Application\FileProcessor;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesCollector;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesProcessor;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Contract\PostRunnerInterface;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\FileSystem\PhpFilesFinder;
use Rector\Core\StaticReflection\DynamicSourceLocatorDecorator;
use Rector\Core\ValueObject\Application\File;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symplify\PackageBuilder\Reflection\PrivatesAccessor;
use Symplify\SmartFileSystem\SmartFileInfo;
use Throwable;
/**
* Rector cycle has 3 steps:
*
* 1. parse all files to nodes
*
* 2. run Rectors on all files and their nodes
*
* 3. print changed content to file or to string diff with "--dry-run"
*/
final class RectorApplication
final class PhpFileProcessor implements FileProcessorInterface
{
/**
* Why 4? One for each cycle, so user sees some activity all the time:
@ -44,14 +34,14 @@ final class RectorApplication
private const PROGRESS_BAR_STEP_MULTIPLIER = 4;
/**
* @var SmartFileInfo[]
* @var Configuration
*/
private $notParsedFiles = [];
private $configuration;
/**
* @var PostRunnerInterface[]
* @var File[]
*/
private $postRunners = [];
private $notParsedFiles = [];
/**
* @var SymfonyStyle
@ -63,11 +53,6 @@ final class RectorApplication
*/
private $errorAndDiffCollector;
/**
* @var Configuration
*/
private $configuration;
/**
* @var FileProcessor
*/
@ -83,41 +68,25 @@ final class RectorApplication
*/
private $removedAndAddedFilesProcessor;
/**
* @var NodeScopeResolver
*/
private $nodeScopeResolver;
/**
* @var PrivatesAccessor
*/
private $privatesAccessor;
/**
* @var PhpFilesFinder
* @var FileDiffFileDecorator
*/
private $phpFilesFinder;
private $fileDiffFileDecorator;
/**
* @var DynamicSourceLocatorDecorator
*/
private $dynamicSourceLocatorDecorator;
/**
* @param PostRunnerInterface[] $postRunners
*/
public function __construct(
Configuration $configuration,
ErrorAndDiffCollector $errorAndDiffCollector,
FileProcessor $fileProcessor,
NodeScopeResolver $nodeScopeResolver,
RemovedAndAddedFilesCollector $removedAndAddedFilesCollector,
RemovedAndAddedFilesProcessor $removedAndAddedFilesProcessor,
SymfonyStyle $symfonyStyle,
PrivatesAccessor $privatesAccessor,
PhpFilesFinder $phpFilesFinder,
DynamicSourceLocatorDecorator $dynamicSourceLocatorDecorator,
array $postRunners
FileDiffFileDecorator $fileDiffFileDecorator
) {
$this->symfonyStyle = $symfonyStyle;
$this->errorAndDiffCollector = $errorAndDiffCollector;
@ -125,55 +94,46 @@ final class RectorApplication
$this->fileProcessor = $fileProcessor;
$this->removedAndAddedFilesCollector = $removedAndAddedFilesCollector;
$this->removedAndAddedFilesProcessor = $removedAndAddedFilesProcessor;
$this->nodeScopeResolver = $nodeScopeResolver;
$this->privatesAccessor = $privatesAccessor;
$this->postRunners = $postRunners;
$this->phpFilesFinder = $phpFilesFinder;
$this->dynamicSourceLocatorDecorator = $dynamicSourceLocatorDecorator;
$this->fileDiffFileDecorator = $fileDiffFileDecorator;
$this->configuration = $configuration;
}
/**
* @param string[] $paths
* @param File[] $files
*/
public function runOnPaths(array $paths): void
public function process(array $files): void
{
$phpFileInfos = $this->phpFilesFinder->findInPaths($paths);
$fileCount = count($phpFileInfos);
$fileCount = count($files);
if ($fileCount === 0) {
return;
}
$this->prepareProgressBar($fileCount);
// PHPStan has to know about all files!
$this->configurePHPStanNodeScopeResolver($phpFileInfos);
// 0. add files and directories to static locator
$this->dynamicSourceLocatorDecorator->addPaths($paths);
// 1. parse files to nodes
$this->parseFileInfosToNodes($phpFileInfos);
$this->parseFileInfosToNodes($files);
// 2. change nodes with Rectors
$this->refactorNodesWithRectors($phpFileInfos);
$this->refactorNodesWithRectors($files);
// 3. apply post rectors
foreach ($phpFileInfos as $phpFileInfo) {
$this->tryCatchWrapper($phpFileInfo, function (SmartFileInfo $smartFileInfo): void {
$this->fileProcessor->postFileRefactor($smartFileInfo);
foreach ($files as $file) {
$this->tryCatchWrapper($file, function (File $file): void {
$this->fileProcessor->postFileRefactor($file);
}, 'post rectors');
}
// 4. print to file or string
foreach ($phpFileInfos as $phpFileInfo) {
// cannot print file with errors, as print would break everything to orignal nodes
if ($this->errorAndDiffCollector->hasErrors($phpFileInfo)) {
$this->advance($phpFileInfo, 'printing');
foreach ($files as $file) {
// cannot print file with errors, as print would break everything to original nodes
if ($this->errorAndDiffCollector->hasSmartFileErrors($file)) {
$this->advance($file, 'printing skipped due error');
continue;
}
$this->tryCatchWrapper($phpFileInfo, function (SmartFileInfo $smartFileInfo): void {
$this->printFileInfo($smartFileInfo);
$this->tryCatchWrapper($file, function (File $file): void {
$this->printFileInfo($file);
}, 'printing');
}
@ -183,11 +143,20 @@ final class RectorApplication
// 4. remove and add files
$this->removedAndAddedFilesProcessor->run();
}
// 5. various extensions on finish
foreach ($this->postRunners as $postRunner) {
$postRunner->run();
}
public function supports(File $file): bool
{
$fileInfo = $file->getSmartFileInfo();
return $fileInfo->hasSuffixes($this->getSupportedFileExtensions());
}
/**
* @return string[]
*/
public function getSupportedFileExtensions(): array
{
return $this->configuration->getFileExtensions();
}
private function prepareProgressBar(int $fileCount): void
@ -204,79 +173,67 @@ final class RectorApplication
}
/**
* @param SmartFileInfo[] $fileInfos
* @param File[] $files
*/
private function configurePHPStanNodeScopeResolver(array $fileInfos): void
private function parseFileInfosToNodes(array $files): void
{
$filePaths = [];
foreach ($fileInfos as $fileInfo) {
$filePaths[] = $fileInfo->getPathname();
}
$this->nodeScopeResolver->setAnalysedFiles($filePaths);
}
/**
* @param SmartFileInfo[] $phpFileInfos
*/
private function parseFileInfosToNodes(array $phpFileInfos): void
{
foreach ($phpFileInfos as $phpFileInfo) {
$this->tryCatchWrapper($phpFileInfo, function (SmartFileInfo $smartFileInfo): void {
$this->fileProcessor->parseFileInfoToLocalCache($smartFileInfo);
foreach ($files as $file) {
$this->tryCatchWrapper($file, function (File $file): void {
$this->fileProcessor->parseFileInfoToLocalCache($file);
}, 'parsing');
}
}
/**
* @param SmartFileInfo[] $phpFileInfos
* @param File[] $files
*/
private function refactorNodesWithRectors(array $phpFileInfos): void
private function refactorNodesWithRectors(array $files): void
{
foreach ($phpFileInfos as $phpFileInfo) {
$this->tryCatchWrapper($phpFileInfo, function (SmartFileInfo $smartFileInfo): void {
$this->fileProcessor->refactor($smartFileInfo);
foreach ($files as $file) {
$this->tryCatchWrapper($file, function (File $file): void {
$this->fileProcessor->refactor($file);
}, 'refactoring');
}
}
private function tryCatchWrapper(SmartFileInfo $smartFileInfo, callable $callback, string $phase): void
private function tryCatchWrapper(File $file, callable $callback, string $phase): void
{
$this->advance($smartFileInfo, $phase);
$this->advance($file, $phase);
try {
if (in_array($smartFileInfo, $this->notParsedFiles, true)) {
if (in_array($file, $this->notParsedFiles, true)) {
// we cannot process this file
return;
}
$callback($smartFileInfo);
$callback($file);
} catch (AnalysedCodeException $analysedCodeException) {
$this->notParsedFiles[] = $smartFileInfo;
$this->errorAndDiffCollector->addAutoloadError($analysedCodeException, $smartFileInfo);
$this->notParsedFiles[] = $file;
$this->errorAndDiffCollector->addAutoloadError($analysedCodeException, $file);
} catch (Throwable $throwable) {
if ($this->symfonyStyle->isVerbose()) {
throw $throwable;
}
$this->errorAndDiffCollector->addThrowableWithFileInfo($throwable, $smartFileInfo);
$fileInfo = $file->getSmartFileInfo();
$this->errorAndDiffCollector->addThrowableWithFileInfo($throwable, $fileInfo);
}
}
private function printFileInfo(SmartFileInfo $fileInfo): void
private function printFileInfo(File $file): void
{
$fileInfo = $file->getSmartFileInfo();
if ($this->removedAndAddedFilesCollector->isFileRemoved($fileInfo)) {
// skip, because this file exists no more
return;
}
$oldContents = $fileInfo->getContents();
$newContent = $this->configuration->isDryRun() ? $this->fileProcessor->printToString($fileInfo)
: $this->fileProcessor->printToFile($fileInfo);
$this->errorAndDiffCollector->addFileDiff($fileInfo, $newContent, $oldContents);
$file->changeFileContent($newContent);
$this->fileDiffFileDecorator->decorate([$file]);
}
/**
@ -299,10 +256,11 @@ final class RectorApplication
$progressBar->setRedrawFrequency($redrawFrequency);
}
private function advance(SmartFileInfo $smartFileInfo, string $phase): void
private function advance(File $file, string $phase): void
{
if ($this->symfonyStyle->isVerbose()) {
$relativeFilePath = $smartFileInfo->getRelativeFilePathFromDirectory(getcwd());
$fileInfo = $file->getSmartFileInfo();
$relativeFilePath = $fileInfo->getRelativeFilePathFromDirectory(getcwd());
$message = sprintf('[%s] %s', $phase, $relativeFilePath);
$this->symfonyStyle->writeln($message);
} elseif ($this->configuration->shouldShowProgressBar()) {

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Autoloading;
use Rector\Core\Configuration\Option;
use Rector\Core\Exception\ShouldNotHappenException;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Throwable;
final class BootstrapFilesIncluder
{
/**
* @var ParameterProvider
*/
private $parameterProvider;
public function __construct(ParameterProvider $parameterProvider)
{
$this->parameterProvider = $parameterProvider;
}
/**
* Inspired by
* @see https://github.com/phpstan/phpstan-src/commit/aad1bf888ab7b5808898ee5fe2228bb8bb4e4cf1
*/
public function includeBootstrapFiles(): void
{
$bootstrapFiles = $this->parameterProvider->provideArrayParameter(Option::BOOTSTRAP_FILES);
foreach ($bootstrapFiles as $bootstrapFile) {
if (! is_file($bootstrapFile)) {
throw new ShouldNotHappenException('Bootstrap file %s does not exist.', $bootstrapFile);
}
try {
require_once $bootstrapFile;
} catch (Throwable $throwable) {
$errorMessage = sprintf(
'"%s" thrown in "%s" on line %d while loading bootstrap file %s: %s',
get_class($throwable),
$throwable->getFile(),
$throwable->getLine(),
$bootstrapFile,
$throwable->getMessage()
);
throw new ShouldNotHappenException($errorMessage, $throwable->getCode(), $throwable);
}
}
}
}

View File

@ -45,11 +45,6 @@ final class Configuration
*/
private $isCacheEnabled = false;
/**
* @var SmartFileInfo[]
*/
private $fileInfos = [];
/**
* @var string[]
*/
@ -148,22 +143,6 @@ final class Configuration
return $this->outputFile;
}
/**
* @param SmartFileInfo[] $fileInfos
*/
public function setFileInfos(array $fileInfos): void
{
$this->fileInfos = $fileInfos;
}
/**
* @return SmartFileInfo[]
*/
public function getFileInfos(): array
{
return $this->fileInfos;
}
public function shouldClearCache(): bool
{
return $this->shouldClearCache;

View File

@ -4,28 +4,31 @@ declare(strict_types=1);
namespace Rector\Core\Console\Command;
use PHPStan\Analyser\NodeScopeResolver;
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\ChangesReporting\Output\ConsoleOutputFormatter;
use Rector\Core\Application\RectorApplication;
use Rector\Core\Application\ApplicationFileProcessor;
use Rector\Core\Autoloading\AdditionalAutoloader;
use Rector\Core\Autoloading\BootstrapFilesIncluder;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Configuration\Option;
use Rector\Core\Console\Output\OutputFormatterCollector;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\FileSystem\PhpFilesFinder;
use Rector\Core\NonPhpFile\NonPhpFileProcessorService;
use Rector\Core\Reporting\MissingRectorRulesReporter;
use Rector\Core\StaticReflection\DynamicSourceLocatorDecorator;
use Rector\Core\ValueObject\ProcessResult;
use Rector\Core\ValueObjectFactory\Application\FileFactory;
use Rector\Core\ValueObjectFactory\ProcessResultFactory;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symplify\PackageBuilder\Console\ShellCode;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Throwable;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ProcessCommand extends Command
{
@ -34,31 +37,16 @@ final class ProcessCommand extends Command
*/
private $additionalAutoloader;
/**
* @var ErrorAndDiffCollector
*/
private $errorAndDiffCollector;
/**
* @var Configuration
*/
private $configuration;
/**
* @var RectorApplication
*/
private $rectorApplication;
/**
* @var OutputFormatterCollector
*/
private $outputFormatterCollector;
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
/**
* @var PhpFilesFinder
*/
@ -74,42 +62,69 @@ final class ProcessCommand extends Command
*/
private $missingRectorRulesReporter;
/**
* @var ParameterProvider
*/
private $parameterProvider;
// /**
// * @var ParameterProvider
// */
// private $parameterProvider;
/**
* @var NonPhpFileProcessorService
* @var ApplicationFileProcessor
*/
private $nonPhpFileProcessorService;
private $applicationFileProcessor;
/**
* @var FileFactory
*/
private $fileFactory;
/**
* @var BootstrapFilesIncluder
*/
private $bootstrapFilesIncluder;
/**
* @var ProcessResultFactory
*/
private $processResultFactory;
/**
* @var NodeScopeResolver
*/
private $nodeScopeResolver;
/**
* @var DynamicSourceLocatorDecorator
*/
private $dynamicSourceLocatorDecorator;
public function __construct(
AdditionalAutoloader $additionalAutoloader,
ChangedFilesDetector $changedFilesDetector,
Configuration $configuration,
ErrorAndDiffCollector $errorAndDiffCollector,
OutputFormatterCollector $outputFormatterCollector,
RectorApplication $rectorApplication,
SymfonyStyle $symfonyStyle,
PhpFilesFinder $phpFilesFinder,
MissingRectorRulesReporter $missingRectorRulesReporter,
ParameterProvider $parameterProvider,
NonPhpFileProcessorService $nonPhpFileProcessorService
ApplicationFileProcessor $applicationFileProcessor,
FileFactory $fileFactory,
BootstrapFilesIncluder $bootstrapFilesIncluder,
ProcessResultFactory $processResultFactory,
NodeScopeResolver $nodeScopeResolver,
DynamicSourceLocatorDecorator $dynamicSourceLocatorDecorator
) {
$this->additionalAutoloader = $additionalAutoloader;
$this->errorAndDiffCollector = $errorAndDiffCollector;
$this->configuration = $configuration;
$this->rectorApplication = $rectorApplication;
$this->outputFormatterCollector = $outputFormatterCollector;
$this->changedFilesDetector = $changedFilesDetector;
$this->symfonyStyle = $symfonyStyle;
$this->phpFilesFinder = $phpFilesFinder;
$this->missingRectorRulesReporter = $missingRectorRulesReporter;
parent::__construct();
$this->parameterProvider = $parameterProvider;
$this->nonPhpFileProcessorService = $nonPhpFileProcessorService;
$this->applicationFileProcessor = $applicationFileProcessor;
$this->fileFactory = $fileFactory;
$this->bootstrapFilesIncluder = $bootstrapFilesIncluder;
$this->processResultFactory = $processResultFactory;
$this->nodeScopeResolver = $nodeScopeResolver;
$this->dynamicSourceLocatorDecorator = $dynamicSourceLocatorDecorator;
}
protected function configure(): void
@ -186,40 +201,31 @@ final class ProcessCommand extends Command
$phpFileInfos = $this->phpFilesFinder->findInPaths($paths);
// register autoloaded and included files
$this->includeBootstrapFiles();
$this->bootstrapFilesIncluder->includeBootstrapFiles();
$this->additionalAutoloader->autoloadWithInputAndSource($input);
if ($this->configuration->isCacheDebug()) {
$message = sprintf('[cache] %d files after cache filter', count($phpFileInfos));
$this->symfonyStyle->note($message);
$this->symfonyStyle->listing($phpFileInfos);
}
// PHPStan has to know about all files!
$this->configurePHPStanNodeScopeResolver($phpFileInfos);
$this->configuration->setFileInfos($phpFileInfos);
$this->rectorApplication->runOnPaths($paths);
$this->nonPhpFileProcessorService->runOnPaths($paths);
// 0. add files and directories to static locator
$this->dynamicSourceLocatorDecorator->addPaths($paths);
$files = $this->fileFactory->createFromPaths($paths);
$this->applicationFileProcessor->run($files);
// report diffs and errors
$outputFormat = (string) $input->getOption(Option::OPTION_OUTPUT_FORMAT);
$outputFormatter = $this->outputFormatterCollector->getByName($outputFormat);
$outputFormatter->report($this->errorAndDiffCollector);
// here should be value obect factory
$processResult = $this->processResultFactory->create($files);
$outputFormatter->report($processResult);
// invalidate affected files
$this->invalidateAffectedCacheFiles();
$this->invalidateCacheChangedFiles($processResult);
// some errors were found → fail
if ($this->errorAndDiffCollector->getErrors() !== []) {
return ShellCode::ERROR;
}
// inverse error code for CI dry-run
if (! $this->configuration->isDryRun()) {
return ShellCode::SUCCESS;
}
if ($this->errorAndDiffCollector->getFileDiffsCount() === 0) {
return ShellCode::SUCCESS;
}
return ShellCode::ERROR;
return $this->resolveReturnCode($processResult);
}
protected function initialize(InputInterface $input, OutputInterface $output): void
@ -245,44 +251,42 @@ final class ProcessCommand extends Command
}
}
private function invalidateAffectedCacheFiles(): void
private function invalidateCacheChangedFiles(ProcessResult $processResult): void
{
if (! $this->configuration->isCacheEnabled()) {
return;
}
foreach ($this->errorAndDiffCollector->getAffectedFileInfos() as $affectedFileInfo) {
$this->changedFilesDetector->invalidateFile($affectedFileInfo);
foreach ($processResult->getChangedFileInfos() as $changedFileInfo) {
$this->changedFilesDetector->invalidateFile($changedFileInfo);
}
}
private function resolveReturnCode(ProcessResult $processResult): int
{
// some errors were found → fail
if ($processResult->getErrors() !== []) {
return ShellCode::ERROR;
}
// inverse error code for CI dry-run
if (! $this->configuration->isDryRun()) {
return ShellCode::SUCCESS;
}
return $processResult->getFileDiffs() === [] ? ShellCode::SUCCESS : ShellCode::ERROR;
}
/**
* Inspired by
* @see https://github.com/phpstan/phpstan-src/commit/aad1bf888ab7b5808898ee5fe2228bb8bb4e4cf1
* @param SmartFileInfo[] $fileInfos
*/
private function includeBootstrapFiles(): void
private function configurePHPStanNodeScopeResolver(array $fileInfos): void
{
$bootstrapFiles = $this->parameterProvider->provideArrayParameter(Option::BOOTSTRAP_FILES);
foreach ($bootstrapFiles as $bootstrapFile) {
if (! is_file($bootstrapFile)) {
throw new ShouldNotHappenException('Bootstrap file %s does not exist.', $bootstrapFile);
}
try {
require_once $bootstrapFile;
} catch (Throwable $throwable) {
$errorMessage = sprintf(
'"%s" thrown in "%s" on line %d while loading bootstrap file %s: %s',
get_class($throwable),
$throwable->getFile(),
$throwable->getLine(),
$bootstrapFile,
$throwable->getMessage()
);
throw new ShouldNotHappenException($errorMessage);
}
$filePaths = [];
foreach ($fileInfos as $fileInfo) {
$filePaths[] = $fileInfo->getPathname();
}
$this->nodeScopeResolver->setAnalysedFiles($filePaths);
}
}

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Contract;
interface PostRunnerInterface
{
public function run(): void;
}

View File

@ -3,14 +3,16 @@ declare(strict_types=1);
namespace Rector\Core\Contract\Processor;
use Rector\Core\ValueObject\NonPhpFile\NonPhpFileChange;
use Symplify\SmartFileSystem\SmartFileInfo;
use Rector\Core\ValueObject\Application\File;
interface FileProcessorInterface
{
public function process(SmartFileInfo $smartFileInfo): ?NonPhpFileChange;
public function supports(File $file): bool;
public function supports(SmartFileInfo $smartFileInfo): bool;
/**
* @param File[] $files
*/
public function process(array $files): void;
/**
* @return string[]

View File

@ -6,10 +6,9 @@ namespace Rector\Core\NonPhpFile;
use Rector\Core\Configuration\RenamedClassesDataCollector;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\ValueObject\NonPhpFile\NonPhpFileChange;
use Rector\Core\ValueObject\Application\File;
use Rector\Core\ValueObject\StaticNonPhpFileSuffixes;
use Rector\PSR4\Collector\RenamedClassesCollector;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @see \Rector\Tests\Renaming\Rector\Name\RenameClassRector\RenameNonPhpTest
@ -41,32 +40,38 @@ final class NonPhpFileProcessor implements FileProcessorInterface
$this->nonPhpFileClassRenamer = $nonPhpFileClassRenamer;
}
public function process(SmartFileInfo $smartFileInfo): ?NonPhpFileChange
/**
* @param File[] $files
*/
public function process(array $files): void
{
$oldContents = $smartFileInfo->getContents();
$classRenames = array_merge(
$this->renamedClassesDataCollector->getOldToNewClasses(),
$this->renamedClassesCollector->getOldToNewClasses()
);
$newContents = $this->nonPhpFileClassRenamer->renameClasses($oldContents, $classRenames);
// nothing has changed
if ($oldContents === $newContents) {
return null;
foreach ($files as $file) {
$this->processFile($file);
}
return new NonPhpFileChange($oldContents, $newContents);
}
public function supports(SmartFileInfo $smartFileInfo): bool
public function supports(File $file): bool
{
return in_array($smartFileInfo->getExtension(), $this->getSupportedFileExtensions(), true);
$fileInfo = $file->getSmartFileInfo();
return $fileInfo->hasSuffixes($this->getSupportedFileExtensions());
}
public function getSupportedFileExtensions(): array
{
return StaticNonPhpFileSuffixes::SUFFIXES;
}
private function processFile(File $file): void
{
$fileInfo = $file->getSmartFileInfo();
$oldFileContents = $fileInfo->getContents();
$classRenames = array_merge(
$this->renamedClassesDataCollector->getOldToNewClasses(),
$this->renamedClassesCollector->getOldToNewClasses()
);
$changedFileContents = $this->nonPhpFileClassRenamer->renameClasses($oldFileContents, $classRenames);
$file->changeFileContent($changedFileContents);
}
}

View File

@ -1,124 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\NonPhpFile;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\FileSystem\FilesFinder;
use Rector\Core\ValueObject\NonPhpFile\NonPhpFileChange;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
final class NonPhpFileProcessorService
{
/**
* @var FileProcessorInterface[]
*/
private $nonPhpFileProcessors = [];
/**
* @var SmartFileSystem
*/
private $smartFileSystem;
/**
* @var Configuration
*/
private $configuration;
/**
* @var ErrorAndDiffCollector
*/
private $errorAndDiffCollector;
/**
* @var FilesFinder
*/
private $filesFinder;
/**
* @param FileProcessorInterface[] $nonPhpFileProcessors
*/
public function __construct(
FilesFinder $filesFinder,
ErrorAndDiffCollector $errorAndDiffCollector,
Configuration $configuration,
SmartFileSystem $smartFileSystem,
array $nonPhpFileProcessors = []
) {
$this->nonPhpFileProcessors = $nonPhpFileProcessors;
$this->smartFileSystem = $smartFileSystem;
$this->filesFinder = $filesFinder;
$this->errorAndDiffCollector = $errorAndDiffCollector;
$this->configuration = $configuration;
}
/**
* @param string[] $paths
*/
public function runOnPaths(array $paths): void
{
$nonPhpFileInfos = $this->collectNonPhpFiles($paths);
$this->runNonPhpFileProcessors($nonPhpFileInfos);
}
/**
* @param SmartFileInfo[] $nonPhpFileInfos
*/
private function runNonPhpFileProcessors(array $nonPhpFileInfos): void
{
foreach ($nonPhpFileInfos as $nonPhpFileInfo) {
foreach ($this->nonPhpFileProcessors as $nonPhpFileProcessor) {
if (! $nonPhpFileProcessor->supports($nonPhpFileInfo)) {
continue;
}
$nonPhpFileChange = $nonPhpFileProcessor->process($nonPhpFileInfo);
if (! $nonPhpFileChange instanceof NonPhpFileChange) {
continue;
}
$this->errorAndDiffCollector->addFileDiff(
$nonPhpFileInfo,
$nonPhpFileChange->getNewContent(),
$nonPhpFileChange->getOldContent()
);
if (! $this->configuration->isDryRun()) {
$this->smartFileSystem->dumpFile(
$nonPhpFileInfo->getPathname(),
$nonPhpFileChange->getNewContent()
);
$this->smartFileSystem->chmod($nonPhpFileInfo->getRealPath(), $nonPhpFileInfo->getPerms());
}
}
}
}
/**
* @param string[] $paths
*
* @return SmartFileInfo[]
*/
private function collectNonPhpFiles(array $paths): array
{
$nonPhpFileExtensions = [];
foreach ($this->nonPhpFileProcessors as $nonPhpFileProcessor) {
$nonPhpFileExtensions = array_merge(
$nonPhpFileProcessor->getSupportedFileExtensions(),
$nonPhpFileExtensions
);
}
$nonPhpFileExtensions = array_unique($nonPhpFileExtensions);
$nonPhpFileInfos = $this->filesFinder->findInDirectoriesAndFiles($paths, $nonPhpFileExtensions);
$composerJsonFilePath = getcwd() . '/composer.json';
if ($this->smartFileSystem->exists($composerJsonFilePath)) {
$nonPhpFileInfos[] = new SmartFileInfo($composerJsonFilePath);
}
return $nonPhpFileInfos;
}
}

View File

@ -21,7 +21,7 @@ final class RectorNodeTraverser extends NodeTraverser
/**
* @var PhpRectorInterface[]
*/
private $allPhpRectors = [];
private $phpRectors = [];
/**
* @var NodeFinder
@ -46,7 +46,8 @@ final class RectorNodeTraverser extends NodeTraverser
/** @var PhpRectorInterface[] $phpRectors */
$phpRectors = $activeRectorsProvider->provideByType(PhpRectorInterface::class);
$this->allPhpRectors = $phpRectors;
$this->phpRectors = $phpRectors;
$this->nodeFinder = $nodeFinder;
$this->currentFileInfoProvider = $currentFileInfoProvider;
}
@ -125,8 +126,8 @@ final class RectorNodeTraverser extends NodeTraverser
return;
}
foreach ($this->allPhpRectors as $allPhpRector) {
$this->addVisitor($allPhpRector);
foreach ($this->phpRectors as $phpRector) {
$this->addVisitor($phpRector);
}
$this->areNodeVisitorsPrepared = true;

View File

@ -4,71 +4,11 @@ declare(strict_types=1);
namespace Rector\Core\PhpParser\Parser;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeFinder;
use PhpParser\Parser;
use PHPStan\Reflection\MethodReflection;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
use Rector\Core\Reflection\FunctionLikeReflectionParser;
final class FunctionLikeParser
/**
* @deprecated For BC layer
*/
final class FunctionLikeParser extends FunctionLikeReflectionParser
{
/**
* @var Parser
*/
private $parser;
/**
* @var SmartFileSystem
*/
private $smartFileSystem;
/**
* @var NodeFinder
*/
private $nodeFinder;
/**
* @var NodeScopeAndMetadataDecorator
*/
private $nodeScopeAndMetadataDecorator;
public function __construct(
Parser $parser,
SmartFileSystem $smartFileSystem,
NodeFinder $nodeFinder,
NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator
) {
$this->parser = $parser;
$this->smartFileSystem = $smartFileSystem;
$this->nodeFinder = $nodeFinder;
$this->nodeScopeAndMetadataDecorator = $nodeScopeAndMetadataDecorator;
}
public function parseMethodReflection(MethodReflection $methodReflection): ?ClassMethod
{
$classReflection = $methodReflection->getDeclaringClass();
$fileName = $classReflection->getFileName();
if (! is_string($fileName)) {
return null;
}
$fileContent = $this->smartFileSystem->readFile($fileName);
if (! is_string($fileContent)) {
return null;
}
$nodes = (array) $this->parser->parse($fileContent);
$nodes = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($nodes, new SmartFileInfo($fileName));
$class = $this->nodeFinder->findFirstInstanceOf($nodes, Class_::class);
if (! $class instanceof Class_) {
return null;
}
return $class->getMethod($methodReflection->getName());
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Reflection;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeFinder;
use PhpParser\Parser;
use PHPStan\Reflection\MethodReflection;
use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
class FunctionLikeReflectionParser
{
/**
* @var Parser
*/
private $parser;
/**
* @var SmartFileSystem
*/
private $smartFileSystem;
/**
* @var NodeFinder
*/
private $nodeFinder;
/**
* @var NodeScopeAndMetadataDecorator
*/
private $nodeScopeAndMetadataDecorator;
public function __construct(
Parser $parser,
SmartFileSystem $smartFileSystem,
NodeFinder $nodeFinder,
NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator
) {
$this->parser = $parser;
$this->smartFileSystem = $smartFileSystem;
$this->nodeFinder = $nodeFinder;
$this->nodeScopeAndMetadataDecorator = $nodeScopeAndMetadataDecorator;
}
public function parseMethodReflection(MethodReflection $methodReflection): ?ClassMethod
{
$classReflection = $methodReflection->getDeclaringClass();
$fileName = $classReflection->getFileName();
if ($fileName === false) {
return null;
}
$fileContent = $this->smartFileSystem->readFile($fileName);
if (! is_string($fileContent)) {
return null;
}
$nodes = (array) $this->parser->parse($fileContent);
$nodes = $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($nodes, new SmartFileInfo($fileName));
$class = $this->nodeFinder->findFirstInstanceOf($nodes, Class_::class);
if (! $class instanceof Class_) {
return null;
}
return $class->getMethod($methodReflection->getName());
}
}

View File

@ -4,31 +4,39 @@ declare(strict_types=1);
namespace Rector\Core\Reporting;
use Rector\Core\PhpParser\NodeTraverser\RectorNodeTraverser;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\PostRector\Contract\Rector\PostRectorInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symplify\PackageBuilder\Console\ShellCode;
final class MissingRectorRulesReporter
{
/**
* @var RectorNodeTraverser
*/
private $rectorNodeTraverser;
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
public function __construct(RectorNodeTraverser $rectorNodeTraverser, SymfonyStyle $symfonyStyle)
/**
* @var RectorInterface[]
*/
private $rectors = [];
/**
* @param RectorInterface[] $rectors
*/
public function __construct(array $rectors, SymfonyStyle $symfonyStyle)
{
$this->rectorNodeTraverser = $rectorNodeTraverser;
$this->symfonyStyle = $symfonyStyle;
$this->rectors = $rectors;
}
public function reportIfMissing(): ?int
{
if ($this->rectorNodeTraverser->getPhpRectorCount() !== 0) {
$activeRectors = array_filter($this->rectors, function (RectorInterface $rector): bool {
return ! $rector instanceof PostRectorInterface;
});
if ($activeRectors !== []) {
return null;
}

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Rector\Core\ValueObject\Application;
use Rector\Core\ValueObject\Reporting\FileDiff;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @see \Rector\Core\ValueObjectFactory\Application\FileFactory
*/
final class File
{
/**
* @var SmartFileInfo
*/
private $smartFileInfo;
/**
* @var string
*/
private $fileContent;
/**
* @var bool
*/
private $hasChanged = false;
/**
* @var string
*/
private $originalFileContent;
/**
* @var FileDiff|null
*/
private $fileDiff;
public function __construct(SmartFileInfo $smartFileInfo, string $fileContent)
{
$this->smartFileInfo = $smartFileInfo;
$this->fileContent = $fileContent;
$this->originalFileContent = $fileContent;
}
public function getSmartFileInfo(): SmartFileInfo
{
return $this->smartFileInfo;
}
public function getFileContent(): string
{
return $this->fileContent;
}
public function changeFileContent(string $newFileContent): void
{
if ($this->fileContent === $newFileContent) {
return;
}
$this->fileContent = $newFileContent;
$this->hasChanged = true;
}
public function getOriginalFileContent(): string
{
return $this->originalFileContent;
}
public function hasChanged(): bool
{
return $this->hasChanged;
}
public function setFileDiff(FileDiff $fileDiff): void
{
$this->fileDiff = $fileDiff;
}
public function getFileDiff(): ?FileDiff
{
return $this->fileDiff;
}
}

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\ValueObject\NonPhpFile;
final class NonPhpFileChange
{
/**
* @var string
*/
private $oldContent;
/**
* @var string
*/
private $newContent;
public function __construct(string $oldContent, string $newContent)
{
$this->oldContent = $oldContent;
$this->newContent = $newContent;
}
public function getOldContent(): string
{
return $this->oldContent;
}
public function getNewContent(): string
{
return $this->newContent;
}
}

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Rector\Core\ValueObject;
use Rector\Core\ValueObject\Application\RectorError;
use Rector\Core\ValueObject\Reporting\FileDiff;
use Symplify\SmartFileSystem\SmartFileInfo;
use Webmozart\Assert\Assert;
/**
* @see \Rector\Core\ValueObjectFactory\ProcessResultFactory
*/
final class ProcessResult
{
/**
* @var FileDiff[]
*/
private $fileDiffs = [];
/**
* @var RectorError[]
*/
private $errors = [];
/**
* @var int
*/
private $addedFilesCount;
/**
* @var int
*/
private $removedFilesCount;
/**
* @var int
*/
private $removedNodeCount;
/**
* @param FileDiff[] $fileDiffs
* @param RectorError[] $errors
*/
public function __construct(
array $fileDiffs,
array $errors,
int $addedFilesCount,
int $removedFilesCount,
int $removedNodeCount
) {
Assert::allIsAOf($fileDiffs, FileDiff::class);
Assert::allIsAOf($errors, RectorError::class);
$this->fileDiffs = $fileDiffs;
$this->errors = $errors;
$this->addedFilesCount = $addedFilesCount;
$this->removedFilesCount = $removedFilesCount;
$this->removedNodeCount = $removedNodeCount;
}
/**
* @return FileDiff[]
*/
public function getFileDiffs(): array
{
return $this->fileDiffs;
}
/**
* @return RectorError[]
*/
public function getErrors(): array
{
return $this->errors;
}
public function getAddedFilesCount(): int
{
return $this->addedFilesCount;
}
public function getRemovedFilesCount(): int
{
return $this->removedFilesCount;
}
public function getRemovedAndAddedFilesCount(): int
{
return $this->removedFilesCount + $this->addedFilesCount;
}
public function getRemovedNodeCount(): int
{
return $this->removedNodeCount;
}
/**
* @return SmartFileInfo[]
*/
public function getChangedFileInfos(): array
{
$fileInfos = [];
foreach ($this->fileDiffs as $fileDiff) {
$fileInfos[] = $fileDiff->getFileInfo();
}
return array_unique($fileInfos);
}
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Rector\Core\ValueObjectFactory\Application;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\FileSystem\FilesFinder;
use Rector\Core\ValueObject\Application\File;
/**
* @see \Rector\Core\ValueObject\Application\File
*/
final class FileFactory
{
/**
* @var FileProcessorInterface[]
*/
private $fileProcessors = [];
/**
* @var FilesFinder
*/
private $filesFinder;
/**
* @param FileProcessorInterface[] $fileProcessors
*/
public function __construct(FilesFinder $filesFinder, array $fileProcessors)
{
$this->fileProcessors = $fileProcessors;
$this->filesFinder = $filesFinder;
}
/**
* @param string[] $paths
* @return File[]
*/
public function createFromPaths(array $paths): array
{
$supportedFileExtensions = $this->resolveSupportedFileExtensions();
$fileInfos = $this->filesFinder->findInDirectoriesAndFiles($paths, $supportedFileExtensions);
$files = [];
foreach ($fileInfos as $fileInfo) {
$files[] = new File($fileInfo, $fileInfo->getContents());
}
return $files;
}
/**
* @return string[]
*/
private function resolveSupportedFileExtensions(): array
{
$supportedFileExtensions = [];
foreach ($this->fileProcessors as $fileProcessor) {
$supportedFileExtensions = array_merge(
$supportedFileExtensions,
$fileProcessor->getSupportedFileExtensions()
);
}
return array_unique($supportedFileExtensions);
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Rector\Core\ValueObjectFactory;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\Core\ValueObject\Application\File;
use Rector\Core\ValueObject\ProcessResult;
final class ProcessResultFactory
{
/**
* @var ErrorAndDiffCollector
*/
private $errorAndDiffCollector;
public function __construct(ErrorAndDiffCollector $errorAndDiffCollector)
{
$this->errorAndDiffCollector = $errorAndDiffCollector;
}
/**
* @param File[] $files
*/
public function create(array $files): ProcessResult
{
$fileDiffs = [];
foreach ($files as $file) {
if ($file->getFileDiff() === null) {
continue;
}
$fileDiffs[] = $file->getFileDiff();
}
return new ProcessResult(
$fileDiffs,
$this->errorAndDiffCollector->getErrors(),
$this->errorAndDiffCollector->getAddedFilesCount(),
$this->errorAndDiffCollector->getRemovedFilesCount(),
$this->errorAndDiffCollector->getRemovedNodeCount(),
);
}
}

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Application\ApplicationFileProcessor;
use Rector\Core\Application\ApplicationFileProcessor;
use Rector\Core\Configuration\Configuration;
use Rector\Core\HttpKernel\RectorKernel;
use Rector\Core\ValueObjectFactory\Application\FileFactory;
use Rector\Core\ValueObjectFactory\ProcessResultFactory;
use Symplify\PackageBuilder\Testing\AbstractKernelTestCase;
final class ApplicationFileProcessorTest extends AbstractKernelTestCase
{
/**
* @var ApplicationFileProcessor
*/
private $applicationFileProcessor;
/**
* @var FileFactory
*/
private $fileFactory;
/**
* @var ProcessResultFactory
*/
private $processResultFactory;
protected function setUp(): void
{
$this->bootKernelWithConfigs(RectorKernel::class, [__DIR__ . '/config/configured_rule.php']);
/** @var Configuration $configuration */
$configuration = $this->getService(Configuration::class);
$configuration->setIsDryRun(true);
$this->applicationFileProcessor = $this->getService(ApplicationFileProcessor::class);
$this->fileFactory = $this->getService(FileFactory::class);
$this->processResultFactory = $this->getService(ProcessResultFactory::class);
}
public function test(): void
{
$files = $this->fileFactory->createFromPaths([__DIR__ . '/Fixture']);
$this->assertCount(2, $files);
$this->applicationFileProcessor->run($files);
$processResult = $this->processResultFactory->create($files);
$this->assertCount(1, $processResult->getFileDiffs());
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Application\ApplicationFileProcessor\Source;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\ValueObject\Application\File;
final class TextFileProcessor implements FileProcessorInterface
{
/**
* @param File[] $files
*/
public function process(array $files): void
{
foreach ($files as $file) {
$this->processFile($file);
}
}
public function supports(File $file): bool
{
$fileInfo = $file->getSmartFileInfo();
return $fileInfo->hasSuffixes($this->getSupportedFileExtensions());
}
/**
* @return string[]
*/
public function getSupportedFileExtensions(): array
{
return ['txt'];
}
private function processFile($file): void
{
$oldFileContent = $file->getFileContent();
$changedFileContent = str_replace('Foo', 'Bar', $oldFileContent);
$file->changeFileContent($changedFileContent);
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Core\Tests\Application\ApplicationFileProcessor\Source\TextFileProcessor;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(TextFileProcessor::class);
};

View File

@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\NonPhpFile;
use Rector\ChangesReporting\Application\ErrorAndDiffCollector;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Configuration\Option;
use Rector\Core\NonPhpFile\NonPhpFileProcessorService;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class NonPhpFileProcessorServiceTest extends AbstractRectorTestCase
{
/**
* @var NonPhpFileProcessorService
*/
private $nonPhpFileProcessorService;
/**
* @var ErrorAndDiffCollector
*/
private $errorAndDiffCollector;
protected function setUp(): void
{
parent::setUp();
/** @var Configuration $configuration */
$configuration = $this->getService(Configuration::class);
$configuration->setIsDryRun(true);
$this->nonPhpFileProcessorService = $this->getService(NonPhpFileProcessorService::class);
$this->errorAndDiffCollector = $this->getService(ErrorAndDiffCollector::class);
}
public function test(): void
{
$this->nonPhpFileProcessorService->runOnPaths($this->parameterProvider->provideParameter(Option::PATHS));
$fileDiffs = $this->errorAndDiffCollector->getFileDiffs();
$this->assertCount(1, $fileDiffs);
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\NonPhpFile\Source;
use Rector\Core\Contract\Processor\FileProcessorInterface;
use Rector\Core\ValueObject\NonPhpFile\NonPhpFileChange;
use Symplify\SmartFileSystem\SmartFileInfo;
final class TextNonPhpFileProcessor implements FileProcessorInterface
{
public function process(SmartFileInfo $smartFileInfo): ?NonPhpFileChange
{
$oldContent = $smartFileInfo->getContents();
$newContent = str_replace('Foo', 'Bar', $oldContent);
return new NonPhpFileChange($oldContent, $newContent);
}
public function supports(SmartFileInfo $smartFileInfo): bool
{
return in_array($smartFileInfo->getExtension(), $this->getSupportedFileExtensions());
}
public function getSupportedFileExtensions(): array
{
return ['txt'];
}
}

View File

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
use Rector\Core\Configuration\Option;
use Rector\Core\Tests\NonPhpFile\Source\TextNonPhpFileProcessor;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(TextNonPhpFileProcessor::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [__DIR__ . '/../Fixture/']);
};

View File

@ -18,7 +18,7 @@ final class AllowedAutoloadedTypeAnalyzer
* @see https://regex101.com/r/BBm9bf/1
* @var string
*/
private const AUTOLOADED_CLASS_PREFIX_REGEX = '#^(PhpParser|PHPStan|Rector|Reflection)#';
private const AUTOLOADED_CLASS_PREFIX_REGEX = '#^(PhpParser|PHPStan|Rector|Reflection|Symfony\\\\Component\\\\Console)#';
/**
* @var array<class-string>

View File

@ -4,13 +4,13 @@ declare(strict_types=1);
namespace Rector\PHPStanExtensions\Tests\Rule\NoInstanceOfStaticReflectionRule\Fixture;
use Symfony\Component\Console\Command\Command;
use Hoa\Math\Sampler\Random;
final class InstanceofWithType
{
public function check($object)
{
if ($object instanceof Command) {
if ($object instanceof Random) {
return true;
}
}

View File

@ -4,12 +4,12 @@ declare(strict_types=1);
namespace Rector\PHPStanExtensions\Tests\Rule\NoInstanceOfStaticReflectionRule\Fixture;
use Symfony\Component\Console\Command\Command;
use Hoa\Math\Sampler\Random;
final class IsAWithType
{
public function check($object)
{
return is_a($object, Command::class);
return is_a($object, Random::class);
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanExtensions\Tests\Rule\NoInstanceOfStaticReflectionRule\Fixture;
use Symfony\Component\Console\Helper\ProgressBar;
final class SkipSymfony
{
public function find($node)
{
return $node instanceof ProgressBar;
}
}

View File

@ -37,6 +37,7 @@ final class NoInstanceOfStaticReflectionRuleTest extends AbstractServiceAwareRul
yield [__DIR__ . '/Fixture/SkipReflection.php', []];
yield [__DIR__ . '/Fixture/SkipDateTime.php', []];
yield [__DIR__ . '/Fixture/SkipTypesArray.php', []];
yield [__DIR__ . '/Fixture/SkipSymfony.php', []];
}
protected function getRule(): Rule