Merge pull request #821 from rectorphp/symfony-tests-2

Testing *ScalarTypehintsRector on Symfony #2
This commit is contained in:
Tomáš Votruba 2018-12-08 16:34:41 +01:00 committed by GitHub
commit 779fb7df61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 475 additions and 362 deletions

View File

@ -71,6 +71,7 @@ parameters:
- 'src/Rector/Constant/RenameClassConstantsUseToStringsRector.php'
- '*/packages/NodeTypeResolver/**/PerNodeTypeResolver/**TypeResolver.php'
- '*/packages/NodeTypeResolver/**/PerNodeTypeResolver/**TypeResolver/*Test.php'
- '*RectorTest.php'
- 'tests/PhpParser/Node/ConstExprEvaluatorFactoryTest.php'
- 'src/Rector/AbstractPHPUnitRector.php'
- 'src/Rector/Class_/ParentClassToTraitsRector.php'

View File

@ -7,13 +7,14 @@ use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\Application\ErrorCollector;
use Rector\Application\ErrorAndDiffCollector;
use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockAnalyzer;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
use function Safe\sprintf;
/**
@ -37,18 +38,18 @@ final class JmsInjectAnnotationRector extends AbstractRector
private $analyzedApplicationContainer;
/**
* @var ErrorCollector
* @var ErrorAndDiffCollector
*/
private $errorCollector;
private $errorAndDiffCollector;
public function __construct(
DocBlockAnalyzer $docBlockAnalyzer,
AnalyzedApplicationContainerInterface $analyzedApplicationContainer,
ErrorCollector $errorCollector
ErrorAndDiffCollector $errorAndDiffCollector
) {
$this->docBlockAnalyzer = $docBlockAnalyzer;
$this->analyzedApplicationContainer = $analyzedApplicationContainer;
$this->errorCollector = $errorCollector;
$this->errorAndDiffCollector = $errorAndDiffCollector;
}
public function getDefinition(): RectorDefinition
@ -137,9 +138,14 @@ CODE_SAMPLE
}
// collect error
$this->errorCollector->addErrorWithRectorMessage(
/** @var SmartFileInfo $fileInfo */
$fileInfo = $node->getAttribute(Attribute::FILE_INFO);
$this->errorAndDiffCollector->addErrorWithRectorClassMessageAndFileInfo(
self::class,
sprintf('Service "%s" was not found in DI Container of your Symfony App.', $serviceName)
sprintf('Service "%s" was not found in DI Container of your Symfony App.', $serviceName),
$fileInfo
);
}

View File

@ -12,7 +12,6 @@ use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Interface_;
use Rector\Application\FilesToReprintCollector;
use Rector\NodeTypeResolver\Application\ClassLikeNodeCollector;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\NodeTypeResolver\Php\AbstractTypeInfo;
@ -45,15 +44,9 @@ abstract class AbstractScalarTypehintRector extends AbstractRector
*/
protected $classLikeNodeCollector;
/**
* @var FilesToReprintCollector
*/
protected $filesToReprintCollector;
public function __construct(
DocBlockAnalyzer $docBlockAnalyzer,
ClassLikeNodeCollector $classLikeNodeCollector,
FilesToReprintCollector $filesToReprintCollector,
bool $enableObjectType = false
) {
$this->docBlockAnalyzer = $docBlockAnalyzer;
@ -62,7 +55,6 @@ abstract class AbstractScalarTypehintRector extends AbstractRector
if ($enableObjectType) {
PhpTypeSupport::enableType('object');
}
$this->filesToReprintCollector = $filesToReprintCollector;
}
/**

View File

@ -8,7 +8,6 @@ use PhpParser\Node\Stmt\Function_;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class ParamScalarTypehintRector extends AbstractScalarTypehintRector
{
@ -148,27 +147,25 @@ CODE_SAMPLE
// update their methods as well
foreach ($childrenClassLikes as $childClassLike) {
$childClassMethod = $childClassLike->getMethod($methodName);
if ($childClassMethod) {
if (! isset($childClassMethod->params[$i])) {
continue;
}
$childClassMethodParam = $childClassMethod->params[$i];
if ($childClassMethodParam->type !== null) {
continue;
}
$childClassMethodParam->type = $this->resolveChildType($paramTagInfo, $node, $paramNode);
// let the method know it was changed now
$childClassMethodParam->type->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);
// reprint the file
/** @var SmartFileInfo $fileInfo */
$fileInfo = $childClassMethod->getAttribute(Attribute::FILE_INFO);
$this->filesToReprintCollector->addFileInfo($fileInfo);
if ($childClassMethod === null) {
continue;
}
if (! isset($childClassMethod->params[$i])) {
continue;
}
$childClassMethodParam = $childClassMethod->params[$i];
if ($childClassMethodParam->type !== null) {
continue;
}
$childClassMethodParam->type = $this->resolveChildType($paramTagInfo, $node, $paramNode);
// let the method know it was changed now
$childClassMethodParam->type->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);
$this->notifyNodeChangeFileInfo($childClassMethodParam);
}
}
}

View File

@ -8,7 +8,6 @@ use PhpParser\Node\Stmt\Function_;
use Rector\NodeTypeResolver\Node\Attribute;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class ReturnScalarTypehintRector extends AbstractScalarTypehintRector
{
@ -121,11 +120,7 @@ CODE_SAMPLE
// let the method now it was changed now
$childClassMethod->returnType->setAttribute(self::HAS_NEW_INHERITED_TYPE, true);
// reprint the file
/** @var SmartFileInfo $fileInfo */
$fileInfo = $childClassMethod->getAttribute(Attribute::FILE_INFO);
$this->filesToReprintCollector->addFileInfo($fileInfo);
$this->notifyNodeChangeFileInfo($childClassMethod);
}
}

View File

@ -50,7 +50,7 @@ parameters:
- '#Call to function in_array\(\) with arguments string, (.*?) and true will always evaluate to false#'
# known values
# - '#Call to an undefined method PhpParser\\Node\\Expr|PhpParser\\Node\\Name|PhpParser\\Node\\Stmt\\Class_::toString\(\)#'
- '#Property Rector\\Configuration\\Configuration::\$source \(array<string>\) does not accept array<bool\|string>#'
- '#Method Rector\\ContributorTools\\Configuration\\ConfigurationFactory::resolveCategoryFromFqnNodeTypes\(\) should return string but returns string\|false#'
- '#Array \(array<PhpParser\\Node\\Expr\\MethodCall>\) does not accept PhpParser\\Node\\Expr#'
- '#Cannot access property \$expr on PhpParser\\Node\\Stmt\|null#'
@ -60,6 +60,9 @@ parameters:
- '#Cannot call method getAttribute\(\) on PhpParser\\Node\\Name\|null#'
- '#Cannot call method getText\(\) on PhpParser\\Comment\\Doc\|null#'
# buggy
- '#Array \(array<Symplify\\PackageBuilder\\FileSystem\\SmartFileInfo>\) does not accept array<string, Symplify\\PackageBuilder\\FileSystem\\SmartFileInfo>#'
# false positive, has annotation type above
- '#Method Rector\\CodeQuality\\Rector\\Foreach_\\SimplifyForeachToCoalescingRector\:\:matchReturnOrAssignNode\(\) should return PhpParser\\Node\\Expr\\Assign\|PhpParser\\Node\\Stmt\\Return_\|null but returns PhpParser\\Node\|null#'
- '#Access to an undefined property PhpParser\\Node::\$(\w+)#'

View File

@ -2,28 +2,29 @@
namespace Rector\Application;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class AppliedRectorCollector
{
/**
* @var string[]
* @var string[][]
*/
private $rectorClasses = [];
private $rectorClassesByFile = [];
public function addRectorClass(string $rectorClass): void
public function addRectorClass(string $rectorClass, SmartFileInfo $smartFileInfo): void
{
$this->rectorClasses[] = $rectorClass;
}
public function reset(): void
{
$this->rectorClasses = [];
$this->rectorClassesByFile[$smartFileInfo->getRealPath()][] = $rectorClass;
}
/**
* @return string[]
*/
public function getRectorClasses(): array
public function getRectorClasses(SmartFileInfo $smartFileInfo): array
{
return array_unique($this->rectorClasses);
if ($this->rectorClassesByFile[$smartFileInfo->getRealPath()]) {
return array_unique($this->rectorClassesByFile[$smartFileInfo->getRealPath()]);
}
return [];
}
}

View File

@ -0,0 +1,110 @@
<?php declare(strict_types=1);
namespace Rector\Application;
use PHPStan\AnalysedCodeException;
use Rector\ConsoleDiffer\DifferAndFormatter;
use Rector\Error\ExceptionCorrector;
use Rector\Reporting\FileDiff;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
use Throwable;
final class ErrorAndDiffCollector
{
/**
* @var Error[]
*/
private $errors = [];
/**
* @var FileDiff[]
*/
private $fileDiffs = [];
/**
* @var DifferAndFormatter
*/
private $differAndFormatter;
/**
* @var AppliedRectorCollector
*/
private $appliedRectorCollector;
/**
* @var ExceptionCorrector
*/
private $exceptionCorrector;
public function __construct(
DifferAndFormatter $differAndFormatter,
AppliedRectorCollector $appliedRectorCollector,
ExceptionCorrector $exceptionCorrector
) {
$this->differAndFormatter = $differAndFormatter;
$this->appliedRectorCollector = $appliedRectorCollector;
$this->exceptionCorrector = $exceptionCorrector;
}
public function addError(Error $error): void
{
$this->errors[] = $error;
}
/**
* @return Error[]
*/
public function getErrors(): array
{
return $this->errors;
}
public function addFileDiff(SmartFileInfo $smartFileInfo, string $newContent, string $oldContent): void
{
if ($newContent === $oldContent) {
return;
}
$appliedRectors = $this->appliedRectorCollector->getRectorClasses($smartFileInfo);
// always keep the most recent diff
$this->fileDiffs[$smartFileInfo->getRealPath()] = new FileDiff(
$smartFileInfo->getRealPath(),
$this->differAndFormatter->diffAndFormat($oldContent, $newContent),
$appliedRectors
);
}
/**
* @return FileDiff[]
*/
public function getFileDiffs(): array
{
return $this->fileDiffs;
}
public function addAutoloadError(AnalysedCodeException $analysedCodeException, SmartFileInfo $fileInfo): void
{
$message = $this->exceptionCorrector->getAutoloadExceptionMessageAndAddLocation($analysedCodeException);
$this->addError(new Error($fileInfo, $message));
}
public function addErrorWithRectorClassMessageAndFileInfo(
string $rectorClass,
string $message,
SmartFileInfo $smartFileInfo
): void {
$this->errors[] = new Error($smartFileInfo, $message, null, $rectorClass);
}
public function addThrowableWithFileInfo(Throwable $throwable, SmartFileInfo $fileInfo): void
{
$rectorClass = $this->exceptionCorrector->matchRectorClass($throwable);
if ($rectorClass) {
$this->addErrorWithRectorClassMessageAndFileInfo($rectorClass, $throwable->getMessage(), $fileInfo);
} else {
$this->addError(new Error($fileInfo, $throwable->getMessage(), $throwable->getCode()));
}
}
}

View File

@ -1,41 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\Application;
use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider;
final class ErrorCollector
{
/**
* @var Error[]
*/
private $errors = [];
/**
* @var CurrentFileInfoProvider
*/
private $currentFileInfoProvider;
public function __construct(CurrentFileInfoProvider $currentFileInfoProvider)
{
$this->currentFileInfoProvider = $currentFileInfoProvider;
}
public function addError(Error $error): void
{
$this->errors[] = $error;
}
/**
* @return Error[]
*/
public function getErrors(): array
{
return $this->errors;
}
public function addErrorWithRectorMessage(string $rectorClass, string $message): void
{
$this->errors[] = new Error($this->currentFileInfoProvider->getSmartFileInfo(), $message, null, $rectorClass);
}
}

View File

@ -64,8 +64,13 @@ final class FileProcessor
$this->currentFileInfoProvider = $currentFileInfoProvider;
}
public function processFile(SmartFileInfo $smartFileInfo): string
public function parseFileInfoToLocalCache(SmartFileInfo $smartFileInfo): void
{
if (isset($this->tokensByFilePath[$smartFileInfo->getRealPath()])) {
// already parsed
return;
}
$this->currentFileInfoProvider->setCurrentFileInfo($smartFileInfo);
[$newStmts, $oldStmts, $oldTokens] = $this->parseAndTraverseFileInfoToNodes($smartFileInfo);
@ -73,41 +78,31 @@ 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];
return $this->formatPerservingPrinter->printToFile($smartFileInfo, $newStmts, $oldStmts, $oldTokens);
// @todo use filesystem cache to save parsing?
}
public function reprintFile(SmartFileInfo $smartFileInfo): string
public function printToFile(SmartFileInfo $smartFileInfo): string
{
// restore tokens
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePath[$smartFileInfo->getRealPath()];
return $this->formatPerservingPrinter->printToFile($smartFileInfo, $newStmts, $oldStmts, $oldTokens);
}
/**
* See https://github.com/nikic/PHP-Parser/issues/344#issuecomment-298162516.
*/
public function processFileToString(SmartFileInfo $smartFileInfo): string
public function printToString(SmartFileInfo $smartFileInfo): string
{
$this->currentFileInfoProvider->setCurrentFileInfo($smartFileInfo);
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePath[$smartFileInfo->getRealPath()];
return $this->formatPerservingPrinter->printToString($newStmts, $oldStmts, $oldTokens);
}
[$newStmts, $oldStmts, $oldTokens] = $this->parseAndTraverseFileInfoToNodes($smartFileInfo);
public function refactor(SmartFileInfo $smartFileInfo): void
{
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePath[$smartFileInfo->getRealPath()];
$newStmts = $this->rectorNodeTraverser->traverse($newStmts);
// store tokens by absolute path, so we don't have to print them right now
// this is needed for new tokens added in "afterTraverse()"
$this->tokensByFilePath[$smartFileInfo->getRealPath()] = [$newStmts, $oldStmts, $oldTokens];
return $this->formatPerservingPrinter->printToString($newStmts, $oldStmts, $oldTokens);
}
/**
* See https://github.com/nikic/PHP-Parser/issues/344#issuecomment-298162516.
*/
public function reprintToString(SmartFileInfo $smartFileInfo): string
{
// restore tokens
[$newStmts, $oldStmts, $oldTokens] = $this->tokensByFilePath[$smartFileInfo->getRealPath()];
return $this->formatPerservingPrinter->printToString($newStmts, $oldStmts, $oldTokens);
}
/**
@ -122,7 +117,6 @@ final class FileProcessor
$oldStmts,
$smartFileInfo->getRealPath()
);
$newStmts = $this->rectorNodeTraverser->traverse($newStmts);
return [$newStmts, $oldStmts, $oldTokens];
}

View File

@ -1,31 +0,0 @@
<?php declare(strict_types=1);
namespace Rector\Application;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
final class FilesToReprintCollector
{
/**
* @var SmartFileInfo[]
*/
private $fileInfos = [];
public function addFileInfo(SmartFileInfo $smartFileInfo): void
{
$this->fileInfos[$smartFileInfo->getRealPath()] = $smartFileInfo;
}
/**
* @return SmartFileInfo[]
*/
public function getFileInfos(): array
{
return $this->fileInfos;
}
public function reset(): void
{
$this->fileInfos = [];
}
}

View File

@ -0,0 +1,134 @@
<?php declare(strict_types=1);
namespace Rector\Application;
use PHPStan\AnalysedCodeException;
use Rector\Configuration\Configuration;
use Rector\FileSystemRector\FileSystemFileProcessor;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symplify\PackageBuilder\FileSystem\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
{
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
/**
* @var FileSystemFileProcessor
*/
private $fileSystemFileProcessor;
/**
* @var ErrorAndDiffCollector
*/
private $errorAndDiffCollector;
/**
* @var Configuration
*/
private $configuration;
/**
* @var FileProcessor
*/
private $fileProcessor;
public function __construct(
SymfonyStyle $symfonyStyle,
FileSystemFileProcessor $fileSystemFileProcessor,
ErrorAndDiffCollector $errorAndDiffCollector,
Configuration $configuration,
FileProcessor $fileProcessor
) {
$this->symfonyStyle = $symfonyStyle;
$this->fileSystemFileProcessor = $fileSystemFileProcessor;
$this->errorAndDiffCollector = $errorAndDiffCollector;
$this->configuration = $configuration;
$this->fileProcessor = $fileProcessor;
}
/**
* @param SmartFileInfo[] $fileInfos
*/
public function runOnFileInfos(array $fileInfos): void
{
$totalFiles = count($fileInfos);
if (! $this->symfonyStyle->isVerbose()) {
// why 3? one for each cycle, so user sees some activity all the time
$this->symfonyStyle->progressStart($totalFiles * 3);
}
// 1. parse files to nodes
foreach ($fileInfos as $fileInfo) {
$this->advance();
$this->fileProcessor->parseFileInfoToLocalCache($fileInfo);
}
// 2. change nodes with Rectors
foreach ($fileInfos as $fileInfo) {
$this->advance();
$this->fileProcessor->refactor($fileInfo);
}
// 3. print to file or string
foreach ($fileInfos as $fileInfo) {
$this->processFileInfo($fileInfo);
if ($this->symfonyStyle->isVerbose()) {
$this->symfonyStyle->writeln($fileInfo->getRealPath());
} else {
$this->symfonyStyle->progressAdvance();
}
}
$this->symfonyStyle->newLine(2);
}
private function processFileInfo(SmartFileInfo $fileInfo): void
{
try {
$oldContent = $fileInfo->getContents();
if ($this->configuration->isDryRun()) {
$newContent = $this->fileProcessor->printToString($fileInfo);
} else {
$newContent = $this->fileProcessor->printToFile($fileInfo);
}
$this->errorAndDiffCollector->addFileDiff($fileInfo, $newContent, $oldContent);
$this->fileSystemFileProcessor->processFileInfo($fileInfo);
} catch (AnalysedCodeException $analysedCodeException) {
if ($this->configuration->shouldHideAutoloadErrors()) {
return;
}
$this->errorAndDiffCollector->addAutoloadError($analysedCodeException, $fileInfo);
} catch (Throwable $throwable) {
if ($this->symfonyStyle->isVerbose()) {
throw $throwable;
}
$this->errorAndDiffCollector->addThrowableWithFileInfo($throwable, $fileInfo);
}
}
private function advance(): void
{
if ($this->symfonyStyle->isVerbose() === false) {
$this->symfonyStyle->progressAdvance();
}
}
}

View File

@ -0,0 +1,63 @@
<?php declare(strict_types=1);
namespace Rector\Configuration;
use Symfony\Component\Console\Input\InputInterface;
final class Configuration
{
/**
* @var bool
*/
private $isDryRun = false;
/**
* Files and directories to by analysed
* @var string[]
*/
private $source = [];
/**
* @var bool
*/
private $hideAutoloadErrors = false;
/**
* @var bool
*/
private $withStyle = false;
/**
* Needs to run in the start of the life cycle, since the rest of workflow uses it.
*/
public function resolveFromInput(InputInterface $input): void
{
$this->isDryRun = (bool) $input->getOption(Option::OPTION_DRY_RUN);
$this->source = (array) $input->getArgument(Option::SOURCE);
$this->hideAutoloadErrors = (bool) $input->getOption(Option::HIDE_AUTOLOAD_ERRORS);
$this->withStyle = (bool) $input->getOption(Option::OPTION_WITH_STYLE);
}
public function isDryRun(): bool
{
return $this->isDryRun;
}
/**
* @return string[]
*/
public function getSource(): array
{
return $this->source;
}
public function shouldHideAutoloadErrors(): bool
{
return $this->hideAutoloadErrors;
}
public function isWithStyle(): bool
{
return $this->withStyle;
}
}

View File

@ -7,6 +7,7 @@ use Nette\Utils\Strings;
use Rector\Console\Shell;
use Rector\ConsoleDiffer\MarkdownDifferAndFormatter;
use Rector\Contract\Rector\RectorInterface;
use Rector\Error\ExceptionCorrector;
use Rector\Exception\ShouldNotHappenException;
use Rector\RectorDefinition\ConfiguredCodeSample;
use ReflectionClass;
@ -169,6 +170,11 @@ final class GenerateRectorOverviewCommand extends Command
$rectors = [];
foreach (array_keys($robotLoader->getIndexedClasses()) as $class) {
// special case, because robot loader is case insensitive
if ($class === ExceptionCorrector::class) {
continue;
}
$reflectionClass = new ReflectionClass($class);
if ($reflectionClass->isAbstract()) {
continue;

View File

@ -2,23 +2,16 @@
namespace Rector\Console\Command;
use PHPStan\AnalysedCodeException;
use Rector\Application\AppliedRectorCollector;
use Rector\Application\Error;
use Rector\Application\ErrorCollector;
use Rector\Application\FileProcessor;
use Rector\Application\FilesToReprintCollector;
use Rector\Application\ErrorAndDiffCollector;
use Rector\Application\RectorApplication;
use Rector\Autoloading\AdditionalAutoloader;
use Rector\CodingStyle\AfterRectorCodingStyle;
use Rector\Configuration\Configuration;
use Rector\Configuration\Option;
use Rector\Console\Output\ProcessCommandReporter;
use Rector\Console\Shell;
use Rector\ConsoleDiffer\DifferAndFormatter;
use Rector\Contract\Rector\RectorInterface;
use Rector\FileSystem\FilesFinder;
use Rector\FileSystemRector\FileSystemFileProcessor;
use Rector\Guard\RectorGuard;
use Rector\Reporting\FileDiff;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@ -26,23 +19,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symplify\PackageBuilder\Console\Command\CommandNaming;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
use Symplify\PackageBuilder\Parameter\ParameterProvider;
use Throwable;
use function Safe\sprintf;
final class ProcessCommand extends Command
{
/**
* @var FileDiff[]
*/
private $fileDiffs = [];
/**
* @var FileProcessor
*/
private $fileProcessor;
/**
* @var SymfonyStyle
*/
@ -58,16 +37,6 @@ final class ProcessCommand extends Command
*/
private $processCommandReporter;
/**
* @var ParameterProvider
*/
private $parameterProvider;
/**
* @var DifferAndFormatter
*/
private $differAndFormatter;
/**
* @var AdditionalAutoloader
*/
@ -79,14 +48,9 @@ final class ProcessCommand extends Command
private $rectorGuard;
/**
* @var FileSystemFileProcessor
* @var ErrorAndDiffCollector
*/
private $fileSystemFileProcessor;
/**
* @var ErrorCollector
*/
private $errorCollector;
private $errorAndDiffCollector;
/**
* @var AfterRectorCodingStyle
@ -94,45 +58,37 @@ final class ProcessCommand extends Command
private $afterRectorCodingStyle;
/**
* @var AppliedRectorCollector
* @var Configuration
*/
private $appliedRectorCollector;
private $configuration;
/**
* @var FilesToReprintCollector
* @var RectorApplication
*/
private $filesToReprintCollector;
private $rectorApplication;
public function __construct(
FileProcessor $fileProcessor,
SymfonyStyle $symfonyStyle,
FilesFinder $phpFilesFinder,
ProcessCommandReporter $processCommandReporter,
ParameterProvider $parameterProvider,
DifferAndFormatter $differAndFormatter,
AdditionalAutoloader $additionalAutoloader,
RectorGuard $rectorGuard,
FileSystemFileProcessor $fileSystemFileProcessor,
ErrorCollector $errorCollector,
ErrorAndDiffCollector $errorAndDiffCollector,
AfterRectorCodingStyle $afterRectorCodingStyle,
AppliedRectorCollector $appliedRectorCollector,
FilesToReprintCollector $filesToReprintCollector
Configuration $configuration,
RectorApplication $rectorApplication
) {
parent::__construct();
$this->fileProcessor = $fileProcessor;
$this->symfonyStyle = $symfonyStyle;
$this->filesFinder = $phpFilesFinder;
$this->processCommandReporter = $processCommandReporter;
$this->parameterProvider = $parameterProvider;
$this->differAndFormatter = $differAndFormatter;
$this->additionalAutoloader = $additionalAutoloader;
$this->rectorGuard = $rectorGuard;
$this->fileSystemFileProcessor = $fileSystemFileProcessor;
$this->errorCollector = $errorCollector;
$this->errorAndDiffCollector = $errorAndDiffCollector;
$this->afterRectorCodingStyle = $afterRectorCodingStyle;
$this->appliedRectorCollector = $appliedRectorCollector;
$this->filesToReprintCollector = $filesToReprintCollector;
$this->configuration = $configuration;
$this->rectorApplication = $rectorApplication;
}
protected function configure(): void
@ -177,141 +133,32 @@ final class ProcessCommand extends Command
$this->rectorGuard->ensureSomeRectorsAreRegistered();
$source = $input->getArgument(Option::SOURCE);
$this->parameterProvider->changeParameter(Option::SOURCE, $source);
$this->parameterProvider->changeParameter(Option::OPTION_DRY_RUN, $input->getOption(Option::OPTION_DRY_RUN));
$this->configuration->resolveFromInput($input);
$phpFileInfos = $this->filesFinder->findInDirectoriesAndFiles($source, ['php']);
$this->additionalAutoloader->autoloadWithInputAndSource($input, $source);
$this->processFileInfos($phpFileInfos, (bool) $input->getOption(Option::HIDE_AUTOLOAD_ERRORS));
$this->rectorApplication->runOnFileInfos($phpFileInfos);
$this->processCommandReporter->reportFileDiffs($this->fileDiffs);
$this->processCommandReporter->reportChangedFiles(array_keys($this->fileDiffs));
$this->processCommandReporter->reportFileDiffs($this->errorAndDiffCollector->getFileDiffs());
if ($this->errorCollector->getErrors()) {
$this->processCommandReporter->reportErrors($this->errorCollector->getErrors());
if ($this->errorAndDiffCollector->getErrors()) {
$this->processCommandReporter->reportErrors($this->errorAndDiffCollector->getErrors());
return Shell::CODE_ERROR;
}
if ($input->getOption(Option::OPTION_WITH_STYLE)) {
if ($this->configuration->isWithStyle()) {
$this->afterRectorCodingStyle->apply($source);
}
$this->symfonyStyle->success('Rector is done!');
if ($this->parameterProvider->provideParameter(Option::OPTION_DRY_RUN) && count($this->fileDiffs)) {
if ($this->configuration->isDryRun() && count($this->errorAndDiffCollector->getFileDiffs())) {
return Shell::CODE_ERROR;
}
return Shell::CODE_SUCCESS;
}
/**
* @param SmartFileInfo[] $fileInfos
*/
private function processFileInfos(array $fileInfos, bool $shouldHideAutoloadErrors): void
{
$totalFiles = count($fileInfos);
if (! $this->symfonyStyle->isVerbose()) {
$this->symfonyStyle->progressStart($totalFiles);
}
foreach ($fileInfos as $fileInfo) {
$this->processFileInfo($fileInfo, $shouldHideAutoloadErrors);
if ($this->symfonyStyle->isVerbose()) {
$this->symfonyStyle->writeln($fileInfo->getRealPath());
} else {
$this->symfonyStyle->progressAdvance();
}
}
$this->symfonyStyle->newLine(2);
}
private function processFileInfo(SmartFileInfo $fileInfo, bool $shouldHideAutoloadErrors): void
{
try {
$this->processFile($fileInfo);
$this->fileSystemFileProcessor->processFileInfo($fileInfo);
} catch (AnalysedCodeException $analysedCodeException) {
if ($shouldHideAutoloadErrors) {
return;
}
$message = sprintf(
'Analyze error: "%s". Try to include your files in "parameters > autoload_paths".%sSee https://github.com/rectorphp/rector#extra-autoloading',
$analysedCodeException->getMessage(),
PHP_EOL
);
$this->errorCollector->addError(new Error($fileInfo, $message));
} catch (Throwable $throwable) {
if ($this->symfonyStyle->isVerbose()) {
throw $throwable;
}
$rectorClass = $this->matchRectorClass($throwable);
if ($rectorClass) {
$this->errorCollector->addErrorWithRectorMessage($rectorClass, $throwable->getMessage());
} else {
$this->errorCollector->addError(new Error($fileInfo, $throwable->getMessage(), $throwable->getCode()));
}
}
}
private function processFile(SmartFileInfo $fileInfo): void
{
$oldContent = $fileInfo->getContents();
if ($this->parameterProvider->provideParameter(Option::OPTION_DRY_RUN)) {
$newContent = $this->fileProcessor->processFileToString($fileInfo);
foreach ($this->filesToReprintCollector->getFileInfos() as $fileInfoToReprint) {
$reprintedOldContent = $fileInfoToReprint->getContents();
$reprintedNewContent = $this->fileProcessor->reprintToString($fileInfoToReprint);
$this->recordFileDiff($fileInfoToReprint, $reprintedNewContent, $reprintedOldContent);
}
} else {
$newContent = $this->fileProcessor->processFile($fileInfo);
foreach ($this->filesToReprintCollector->getFileInfos() as $fileInfoToReprint) {
$reprintedOldContent = $fileInfoToReprint->getContents();
$reprintedNewContent = $this->fileProcessor->reprintFile($fileInfoToReprint);
$this->recordFileDiff($fileInfoToReprint, $reprintedNewContent, $reprintedOldContent);
}
}
$this->recordFileDiff($fileInfo, $newContent, $oldContent);
$this->filesToReprintCollector->reset();
}
private function matchRectorClass(Throwable $throwable): ?string
{
if (! isset($throwable->getTrace()[0])) {
return null;
}
/** @var string $class */
$class = $throwable->getTrace()[0]['class'];
if (! is_a($class, RectorInterface::class, true)) {
return null;
}
return $class;
}
private function recordFileDiff(SmartFileInfo $fileInfo, string $newContent, string $oldContent): void
{
if ($newContent === $oldContent) {
return;
}
// always keep the most recent diff
$this->fileDiffs[$fileInfo->getRealPath()] = new FileDiff(
$fileInfo->getRealPath(),
$this->differAndFormatter->diffAndFormat($oldContent, $newContent),
$this->appliedRectorCollector->getRectorClasses()
);
}
}

View File

@ -6,7 +6,6 @@ use Rector\Application\Error;
use Rector\Reporting\FileDiff;
use Symfony\Component\Console\Style\SymfonyStyle;
use function Safe\ksort;
use function Safe\sort;
use function Safe\sprintf;
final class ProcessCommandReporter
@ -21,23 +20,6 @@ final class ProcessCommandReporter
$this->symfonyStyle = $symfonyStyle;
}
/**
* @param string[] $changedFiles
*/
public function reportChangedFiles(array $changedFiles): void
{
if (count($changedFiles) <= 0) {
return;
}
sort($changedFiles);
$this->symfonyStyle->title(
sprintf('%d Changed file%s', count($changedFiles), count($changedFiles) === 1 ? '' : 's')
);
$this->symfonyStyle->listing($changedFiles);
}
/**
* @param FileDiff[] $fileDiffs
*/

View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace Rector\Error;
use PHPStan\AnalysedCodeException;
use Rector\Contract\Rector\RectorInterface;
use Throwable;
use function Safe\sprintf;
final class ExceptionCorrector
{
public function matchRectorClass(Throwable $throwable): ?string
{
if (! isset($throwable->getTrace()[0])) {
return null;
}
/** @var string $class */
$class = $throwable->getTrace()[0]['class'];
if (! is_a($class, RectorInterface::class, true)) {
return null;
}
return $class;
}
public function getAutoloadExceptionMessageAndAddLocation(AnalysedCodeException $analysedCodeException): string
{
return sprintf(
'Analyze error: "%s". Include your files in "parameters > autoload_paths".%sSee https://github.com/rectorphp/rector#extra-autoloading',
$analysedCodeException->getMessage(),
PHP_EOL
);
}
}

View File

@ -9,6 +9,10 @@ use PhpParser\Node\Stmt\Expression;
use PhpParser\NodeVisitorAbstract;
use Rector\Application\AppliedRectorCollector;
use Rector\Contract\Rector\PhpRectorInterface;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\Attribute;
use Symplify\PackageBuilder\FileSystem\SmartFileInfo;
use function Safe\sprintf;
abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorInterface
{
@ -32,17 +36,6 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
$this->appliedRectorCollector = $appliedRectorCollector;
}
/**
* @param Node[] $nodes
* @return Node[]|null
*/
public function beforeTraverse(array $nodes): ?array
{
$this->appliedRectorCollector->reset();
return parent::beforeTraverse($nodes);
}
/**
* @return int|Node|null
*/
@ -60,7 +53,12 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
// changed!
if ($originalNode !== $node) {
$this->appliedRectorCollector->addRectorClass(static::class);
// transfer attributes that are needed further
if ($node->getAttribute(Attribute::FILE_INFO) === null) {
$node->setAttribute(Attribute::FILE_INFO, $originalNode->getAttribute(Attribute::FILE_INFO));
}
$this->notifyNodeChangeFileInfo($node);
}
if ($originalNode instanceof Stmt && $node instanceof Expr) {
@ -92,6 +90,22 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
return $nodes;
}
protected function notifyNodeChangeFileInfo(Node $node): void
{
/** @var SmartFileInfo|null $fileInfo */
$fileInfo = $node->getAttribute(Attribute::FILE_INFO);
if ($fileInfo === null) {
throw new ShouldNotHappenException(sprintf(
'Node is missing "%s" attribute.%sYou probably created a new node and forgot to move attributes of old one in "%s".',
Attribute::FILE_INFO,
PHP_EOL,
static::class
));
}
$this->appliedRectorCollector->addRectorClass(static::class, $fileInfo);
}
private function isMatchingNodeType(string $nodeClass): bool
{
foreach ($this->getNodeTypes() as $nodeType) {

View File

@ -51,7 +51,7 @@ trait NodeCommandersTrait
{
$this->nodeAddingCommander->addNodeAfterNode($node, $positionNode);
$this->appliedRectorCollector->addRectorClass(static::class);
$this->notifyNodeChangeFileInfo($positionNode);
}
protected function addPropertyToClass(Class_ $classNode, string $propertyType, string $propertyName): void
@ -59,13 +59,13 @@ trait NodeCommandersTrait
$variableInfo = new VariableInfo($propertyName, $propertyType);
$this->propertyAddingCommander->addPropertyToClass($variableInfo, $classNode);
$this->appliedRectorCollector->addRectorClass(static::class);
$this->notifyNodeChangeFileInfo($classNode);
}
protected function removeNode(Node $node): void
{
$this->nodeRemovingCommander->addNode($node);
$this->appliedRectorCollector->addRectorClass(static::class);
$this->notifyNodeChangeFileInfo($node);
}
}

View File

@ -173,7 +173,7 @@ abstract class AbstractRectorTestCase extends TestCase
private function createTemporaryPathWithPrefix(SmartFileInfo $smartFileInfo, string $prefix): string
{
$hash = Strings::substring(md5($smartFileInfo->getPathname()), 0, 5);
$hash = Strings::substring(md5($smartFileInfo->getRealPath()), 0, 5);
return sprintf(
sys_get_temp_dir() . '/rector_temp_tests/%s_%s_%s',
@ -187,7 +187,12 @@ abstract class AbstractRectorTestCase extends TestCase
{
$this->parameterProvider->changeParameter(Option::SOURCE, [$originalFile]);
$changedContent = $this->fileProcessor->processFileToString(new SmartFileInfo($originalFile));
$smartFileInfo = new SmartFileInfo($originalFile);
// life-cycle trio :)
$this->fileProcessor->parseFileInfoToLocalCache($smartFileInfo);
$this->fileProcessor->refactor($smartFileInfo);
$changedContent = $this->fileProcessor->printToString($smartFileInfo);
$this->assertStringEqualsFile($expectedFile, $changedContent);
}

View File

@ -2,7 +2,6 @@
namespace Rector\Tests\Rector\Namespace_\PseudoNamespaceToNamespaceRector;
use PHPUnit_Framework_MockObject_MockObject;
use Rector\Rector\Namespace_\PseudoNamespaceToNamespaceRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
@ -30,7 +29,8 @@ final class PseudoNamespaceToNamespaceRectorTest extends AbstractRectorTestCase
protected function getRectorConfiguration(): array
{
return [
'PHPUnit_' => [PHPUnit_Framework_MockObject_MockObject::class],
// namespace prefix => excluded classes
'PHPUnit_' => ['PHPUnit_Framework_MockObject_MockObject'],
'ChangeMe_' => ['KeepMe_'],
];
}