[SymfonyPHPConfig] Add monorepo split for value objects function (#4153)

* add monorepo split for symfony-php-config

* [SOLID] Prevent replacing referenced parametes with inlined constnat value

* make Symfon 5.0- compatible

* add support for reference skip in constructor

* [rector] add support for reference skip in constructor

* [cs] add support for reference skip in constructor

* improve misisng rule feedback

* underscore

* Fix ChangeReadOnlyVariableWithDefaultValueToConstantRector for new

* add clear-cache to all commands

* colors

* [rector] colors

* [cs] colors

Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
Tomas Votruba 2020-10-10 19:42:43 +02:00 committed by GitHub
parent 35537dba84
commit 4a76cdaa82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 621 additions and 118 deletions

View File

@ -22,7 +22,7 @@ jobs:
-
name: 'Show command'
run: bin/rector show --config rector-ci.php
run: bin/rector show --config rector-ci.php --ansi
-
name: Validate Fixtures
@ -49,6 +49,10 @@ jobs:
name: 'PHP Linter'
run: vendor/bin/parallel-lint src tests packages --exclude packages/rector-generator/templates
-
name: 'Validate Monorepo'
run: composer validate-monorepo
name: ${{ matrix.actions.name }}
runs-on: ubuntu-latest

View File

@ -47,6 +47,7 @@ expectedArguments(
\Rector\NodeTypeResolver\Node\AttributeKey::VIRTUAL_NODE,
\Rector\NodeTypeResolver\Node\AttributeKey::CLOSURE_NODE,
\Rector\NodeTypeResolver\Node\AttributeKey::PARAMETER_POSITION,
\Rector\NodeTypeResolver\Node\AttributeKey::ARGUMENT_POSITION,
);
expectedArguments(
@ -79,6 +80,7 @@ expectedArguments(
\Rector\NodeTypeResolver\Node\AttributeKey::VIRTUAL_NODE,
\Rector\NodeTypeResolver\Node\AttributeKey::CLOSURE_NODE,
\Rector\NodeTypeResolver\Node\AttributeKey::PARAMETER_POSITION,
\Rector\NodeTypeResolver\Node\AttributeKey::ARGUMENT_POSITION,
);
expectedArguments(

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\Core\Bootstrap\ConfigShifter;
use Rector\Core\Bootstrap\NoRectorsLoadedReporter;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Configuration\MinimalVersionChecker;
use Rector\Core\Configuration\MinimalVersionChecker\ComposerJsonParser;
@ -13,11 +14,11 @@ use Rector\Core\Bootstrap\RectorConfigsResolver;
use Rector\Core\Console\Application;
use Rector\Core\Console\Style\SymfonyStyleFactory;
use Rector\Core\DependencyInjection\RectorContainerFactory;
use Rector\Core\Exception\NoRectorsLoadedException;
use Symplify\SetConfigResolver\Bootstrap\InvalidSetReporter;
use Symplify\PackageBuilder\Console\ShellCode;
use Symplify\PackageBuilder\Reflection\PrivatesCaller;
use Symplify\SetConfigResolver\Exception\SetNotFoundException;
use Symplify\SmartFileSystem\SmartFileInfo;
@ini_set('memory_limit', '-1'); // @ intentionally: continue anyway
@ -78,6 +79,7 @@ try {
exit(ShellCode::ERROR);
}
/** @var Application $application */
$application = $container->get(Application::class);
exit($application->run());

View File

@ -152,11 +152,11 @@
"Rector\\DowngradePhp73\\": "rules/downgrade-php73/src",
"Rector\\DowngradePhp74\\": "rules/downgrade-php74/src",
"Rector\\DowngradePhp80\\": "rules/downgrade-php80/src",
"Rector\\SymfonyPhpConfig\\": "rules/symfony-php-config/src"
"Rector\\SymfonyPhpConfig\\": ["packages/symfony-php-config/src", "rules/symfony-php-config/src"]
},
"files": [
"rules/symfony-php-config/functions/functions.php",
"rules/restoration/tests/Rector/Use_/RestoreFullyQualifiedNameRector/Source/ShortClassOnly.php"
"rules/restoration/tests/Rector/Use_/RestoreFullyQualifiedNameRector/Source/ShortClassOnly.php",
"packages/symfony-php-config/functions/functions.php"
]
},
"autoload-dev": {
@ -243,7 +243,7 @@
"Rector\\DowngradePhp73\\Tests\\": "rules/downgrade-php73/tests",
"Rector\\DowngradePhp74\\Tests\\": "rules/downgrade-php74/tests",
"Rector\\DowngradePhp80\\Tests\\": "rules/downgrade-php80/tests",
"Rector\\SymfonyPhpConfig\\Tests\\": "rules/symfony-php-config/tests",
"Rector\\SymfonyPhpConfig\\Tests\\": ["rules/symfony-php-config/tests", "packages/symfony-php-config/tests"],
"Rector\\PHPStanStaticTypeMapper\\Tests\\": "packages/phpstan-static-type-mapper/tests"
},
"classmap": [
@ -274,7 +274,8 @@
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/AnotherClass.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/YetAnotherClass.php",
"rules/solid/tests/Rector/ClassMethod/UseInterfaceOverImplementationInConstructorRector/Source/Coconut.php",
"stubs/Nette/Localization/ITranslation.php"
"stubs/Nette/Localization/ITranslation.php",
"vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php"
]
},
"scripts": {
@ -310,7 +311,10 @@
],
"rector-ci": "bin/rector process --config rector-ci.php --dry-run --ansi",
"rector": "bin/rector process --config rector-ci.php --ansi",
"release": "vendor/bin/monorepo-builder release patch --ansi"
"release": "vendor/bin/monorepo-builder release patch --ansi",
"merge": "vendor/bin/monorepo-builder merge --ansi",
"propagate": "vendor/bin/monorepo-builder propagate --ansi",
"validate-monorepo": "vendor/bin/monorepo-builder validate --ansi"
},
"config": {
"sort-packages": true

View File

@ -11,6 +11,7 @@ use PhpParser\NodeFinder;
use PhpParser\NodeVisitor\CloningVisitor;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use Rector\Core\Bootstrap\NoRectorsLoadedReporter;
use Rector\Core\Configuration\MinimalVersionChecker;
use Rector\Core\Console\Application;
use Rector\Core\EventDispatcher\AutowiredEventDispatcher;
@ -63,6 +64,8 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->alias(SymfonyApplication::class, Application::class);
$services->set(NoRectorsLoadedReporter::class);
$services->set(TextDescriptor::class);
$services->set(ParserFactory::class);

View File

@ -13,4 +13,9 @@ return static function (ContainerConfigurator $containerConfigurator): void {
// release workers - in order to execute
$services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\TagVersionReleaseWorker::class);
$services->set(Symplify\MonorepoBuilder\Release\ReleaseWorker\PushTagReleaseWorker::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::DIRECTORIES_TO_REPOSITORIES, [
__DIR__ . '/packages/symfony-php-config' => 'git@github.com:rectorphp/symfony-php-config.git',
]);
};

View File

@ -119,7 +119,7 @@ final class ConsoleOutputFormatter implements OutputFormatterInterface
$this->symfonyStyle->newLine();
if ($fileDiff->getRectorChanges() !== []) {
$this->symfonyStyle->writeln('Applied rules:');
$this->symfonyStyle->writeln('<options=underscore>Applied rules:</>');
$this->symfonyStyle->newLine();
$this->symfonyStyle->listing($fileDiff->getRectorClasses());
$this->symfonyStyle->newLine();

View File

@ -199,4 +199,9 @@ final class AttributeKey
* @var string
*/
public const PARAMETER_POSITION = 'parameter_position';
/**
* @var string
*/
public const ARGUMENT_POSITION = 'argument_position';
}

View File

@ -13,7 +13,7 @@ use PhpParser\NodeVisitor\NodeConnectingVisitor;
use Rector\Core\Configuration\Configuration;
use Rector\NodeCollector\NodeVisitor\NodeCollectorNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\FileInfoNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\FunctionLikeParamPositionNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\FunctionLikeParamArgPositionNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\FunctionMethodAndClassNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\NamespaceNodeVisitor;
use Rector\NodeTypeResolver\NodeVisitor\PhpDocInfoNodeVisitor;
@ -74,9 +74,9 @@ final class NodeScopeAndMetadataDecorator
private $nodeConnectingVisitor;
/**
* @var FunctionLikeParamPositionNodeVisitor
* @var FunctionLikeParamArgPositionNodeVisitor
*/
private $functionLikeParamPositionNodeVisitor;
private $functionLikeParamArgPositionNodeVisitor;
public function __construct(
CloningVisitor $cloningVisitor,
@ -89,7 +89,7 @@ final class NodeScopeAndMetadataDecorator
PhpDocInfoNodeVisitor $phpDocInfoNodeVisitor,
StatementNodeVisitor $statementNodeVisitor,
NodeConnectingVisitor $nodeConnectingVisitor,
FunctionLikeParamPositionNodeVisitor $functionLikeParamPositionNodeVisitor
FunctionLikeParamArgPositionNodeVisitor $functionLikeParamArgPositionNodeVisitor
) {
$this->phpStanNodeScopeResolver = $phpStanNodeScopeResolver;
$this->cloningVisitor = $cloningVisitor;
@ -101,7 +101,7 @@ final class NodeScopeAndMetadataDecorator
$this->configuration = $configuration;
$this->phpDocInfoNodeVisitor = $phpDocInfoNodeVisitor;
$this->nodeConnectingVisitor = $nodeConnectingVisitor;
$this->functionLikeParamPositionNodeVisitor = $functionLikeParamPositionNodeVisitor;
$this->functionLikeParamArgPositionNodeVisitor = $functionLikeParamArgPositionNodeVisitor;
}
/**
@ -141,7 +141,7 @@ final class NodeScopeAndMetadataDecorator
$nodeTraverser->addVisitor($this->functionMethodAndClassNodeVisitor);
$nodeTraverser->addVisitor($this->namespaceNodeVisitor);
$nodeTraverser->addVisitor($this->phpDocInfoNodeVisitor);
$nodeTraverser->addVisitor($this->functionLikeParamPositionNodeVisitor);
$nodeTraverser->addVisitor($this->functionLikeParamArgPositionNodeVisitor);
$nodes = $nodeTraverser->traverse($nodes);

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeVisitor;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\FunctionLike;
use PhpParser\NodeVisitorAbstract;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class FunctionLikeParamArgPositionNodeVisitor extends NodeVisitorAbstract
{
/**
* @return Node
*/
public function enterNode(Node $node): ?Node
{
if ($node instanceof FunctionLike) {
foreach ($node->getParams() as $position => $param) {
$param->setAttribute(AttributeKey::PARAMETER_POSITION, $position);
}
}
if ($node instanceof MethodCall || $node instanceof StaticCall || $node instanceof FuncCall || $node instanceof New_) {
foreach ($node->args as $position => $arg) {
$arg->setAttribute(AttributeKey::ARGUMENT_POSITION, $position);
}
}
return $node;
}
}

View File

@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\NodeTypeResolver\NodeVisitor;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\NodeVisitorAbstract;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class FunctionLikeParamPositionNodeVisitor extends NodeVisitorAbstract
{
/**
* @return Node
*/
public function enterNode(Node $node): ?Node
{
if (! $node instanceof FunctionLike) {
return null;
}
foreach ($node->getParams() as $position => $param) {
$param->setAttribute(AttributeKey::PARAMETER_POSITION, $position);
}
return $node;
}
}

View File

@ -0,0 +1,37 @@
{
"name": "rector/symfony-php-config",
"description": "Tools that easy work with Symfony PHP Configs",
"license": "MIT",
"authors": [
{
"name": "Tomas Votruba",
"email": "tomas.vot@gmail.com",
"homepage": "https://tomasvotruba.com"
}
],
"require": {
"php": "^7.2.4|^8.0",
"symfony/dependency-injection": "^4.4.8|^5.1",
"symfony/http-kernel": "^4.4.8|^5.0.6",
"symplify/package-builder": "^8.3.29"
},
"require-dev": {
"phpunit/phpunit": "^8.5|^9.2"
},
"autoload": {
"psr-4": {
"Rector\\SymfonyPhpConfig\\": "src"
},
"files": [
"functions/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Rector\\SymfonyPhpConfig\\Tests\\": "tests"
},
"files": [
"../../vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php"
]
}
}

View File

@ -4,12 +4,13 @@ declare(strict_types=1);
namespace Rector\SymfonyPhpConfig;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\SymfonyPhpConfig\Exception\ValueObjectException;
use Rector\SymfonyPhpConfig\Reflection\ArgumentAndParameterFactory;
use ReflectionClass;
use function Symfony\Component\DependencyInjection\Loader\Configurator\inline;
use function Symfony\Component\DependencyInjection\Loader\Configurator\inline_service;
use Symfony\Component\DependencyInjection\Loader\Configurator\InlineServiceConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\ref;
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
@ -27,10 +28,10 @@ function inline_single_object(object $object, ServicesConfigurator $servicesConf
$servicesConfigurator->set(ArgumentAndParameterFactory::class);
$servicesConfigurator->set($className)
->factory([service(ArgumentAndParameterFactory::class), 'create'])
->factory([ref(ArgumentAndParameterFactory::class), 'create'])
->args([$className, $argumentValues, $propertyValues]);
return service($className);
return ref($className);
}
function inline_value_object(object $object): InlineServiceConfigurator
@ -78,11 +79,11 @@ function resolve_argument_values(ReflectionClass $reflectionClass, object $objec
'Constructor for "%s" was not found. Be sure to use only value objects',
$reflectionClass->getName()
);
throw new ShouldNotHappenException($message);
throw new ValueObjectException($message);
}
foreach ($constructorMethodReflection->getParameters() as $constructorParameter) {
$parameterName = $constructorParameter->getName();
foreach ($constructorMethodReflection->getParameters() as $reflectionParameter) {
$parameterName = $reflectionParameter->getName();
$propertyReflection = $reflectionClass->getProperty($parameterName);
$propertyReflection->setAccessible(true);

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\SymfonyPhpConfig\Exception;
use Exception;
final class ValueObjectException extends Exception
{
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Rector\SymfonyPhpConfig\Tests\Functions;
use PHPUnit\Framework\TestCase;
use function Rector\SymfonyPhpConfig\inline_single_object;
use Rector\SymfonyPhpConfig\Tests\Functions\Source\SomeValueObject;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
final class InlineValueObjectTest extends TestCase
{
public function test(): void
{
$servicesConfigurator = $this->createServiceConfigurator();
$someValueObject = new SomeValueObject('Rector');
$referenceConfigurator = inline_single_object($someValueObject, $servicesConfigurator);
$this->assertInstanceOf(ReferenceConfigurator::class, $referenceConfigurator);
$id = (string) $referenceConfigurator;
$this->assertSame(SomeValueObject::class, $id);
}
private function createServiceConfigurator(): ServicesConfigurator
{
$containerBuilder = new ContainerBuilder();
$phpFileLoader = new PhpFileLoader($containerBuilder, new FileLocator());
$instanceOf = [];
return new ServicesConfigurator($containerBuilder, $phpFileLoader, $instanceOf);
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Rector\SymfonyPhpConfig\Tests\Functions\Source;
final class SomeValueObject
{
/**
* @var string
*/
private $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}

View File

@ -51,10 +51,19 @@ final class ExistingClassesProvider
$composerJsonFilePath = getcwd() . '/composer.json';
$composerJson = $this->composerJsonFactory->createFromFilePath($composerJsonFilePath);
$psr4Paths = $composerJson->getAutoload()['psr-4'] ?? [];
$directories = [];
foreach ($composerJson->getAutoload()['psr-4'] ?? [] as $paths) {
if (is_array($paths)) {
$directories = array_merge($directories, $paths);
} else {
$directories[] = $paths;
}
}
$classmapPaths = $composerJson->getAutoload()['classmap'] ?? [];
return array_merge($psr4Paths, $classmapPaths);
return array_merge($directories, $classmapPaths);
}
/**

View File

@ -180,12 +180,12 @@ CODE_SAMPLE
/** @var Variable|ClassConstFetch $variable */
$variable = $readOnlyVariableAssign->var;
// already overriden
// already overridden
if (! $variable instanceof Variable) {
continue;
}
$classConst = $this->createClassConst($variable, $readOnlyVariableAssign->expr);
$classConst = $this->createPrivateClassConst($variable, $readOnlyVariableAssign->expr);
// replace $variable usage in the code with constant
$this->addConstantToClass($class, $classConst);
@ -199,7 +199,7 @@ CODE_SAMPLE
}
}
private function createClassConst(Variable $variable, Expr $expr): ClassConst
private function createPrivateClassConst(Variable $variable, Expr $expr): ClassConst
{
$constantName = $this->createConstantNameFromVariable($variable);

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\SOLID\Tests\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector\Fixture;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
class SkipReferenceInSerivceConfigurator
{
public function run()
{
$containerBuilder = $this->createContainerBuilder();
$phpFileLoader = $this->createPhpFileLoader($containerBuilder);
$instanceOf = [];
return new ServicesConfigurator($containerBuilder, $phpFileLoader, $instanceOf);
}
private function createPhpFileLoader(ContainerBuilder $containerBuilder): PhpFileLoader
{
return new PhpFileLoader($containerBuilder, new FileLocator());
}
private function createContainerBuilder(): ContainerBuilder
{
return new ContainerBuilder();
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Rector\SOLID\Tests\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector\Fixture;
class SkipReferenced
{
public function run()
{
$value = [];
$this->process($value);
}
private function process(array &$value)
{
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Rector\SOLID\Tests\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector\Fixture;
use Rector\SOLID\Tests\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector\Source\ReferenceInConstructor;
class SkipReferencedInConstructor
{
public function run()
{
$value = 'name';
$servicesConfigurator = new ReferenceInConstructor($value);
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Rector\SOLID\Tests\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector\Source;
final class ReferenceInConstructor
{
public function __construct(string &$value)
{
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Bootstrap;
use Symfony\Component\Console\Style\SymfonyStyle;
final class NoRectorsLoadedReporter
{
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
public function __construct(SymfonyStyle $symfonyStyle)
{
$this->symfonyStyle = $symfonyStyle;
}
public function report(): void
{
$this->symfonyStyle->error('We could not find any Rector rules to run');
$this->symfonyStyle->writeln('You have few options to add them:');
$this->symfonyStyle->newLine();
$this->symfonyStyle->title('Add single rule to "rector.php"');
$this->symfonyStyle->writeln(' $services = $containerConfigurator->services();');
$this->symfonyStyle->writeln(' $services->set(...);');
$this->symfonyStyle->newLine(2);
$this->symfonyStyle->title('Add set of rules to "rector.php"');
$this->symfonyStyle->writeln(' $parameters = $containerConfigurator->parameters();');
$this->symfonyStyle->writeln(' $parameters->set(Option::SET, [...]);');
$this->symfonyStyle->newLine(2);
$this->symfonyStyle->title('Missing "rector.php" in your project? Let Rector create it for you');
$this->symfonyStyle->writeln(' vendor/bin/rector init');
$this->symfonyStyle->newLine();
}
}

View File

@ -55,7 +55,6 @@ final class RectorConfigsResolver
/**
* @return SmartFileInfo[]
* @noRector
*/
public function provide(): array
{

View File

@ -155,4 +155,19 @@ final class Option
* @var string
*/
public const OPTION_DEBUG = 'debug';
/**
* @var string
*/
public const XDEBUG = 'xdebug';
/**
* @var string
*/
public const OPTION_SET = 'set';
/**
* @var string
*/
public const OPTION_CONFIG = 'config';
}

View File

@ -8,8 +8,11 @@ use Composer\XdebugHandler\XdebugHandler;
use OutOfBoundsException;
use Rector\ChangesReporting\Output\CheckstyleOutputFormatter;
use Rector\ChangesReporting\Output\JsonOutputFormatter;
use Rector\Core\Bootstrap\NoRectorsLoadedReporter;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Configuration\Option;
use Rector\Core\Exception\Configuration\InvalidConfigurationException;
use Rector\Core\Exception\NoRectorsLoadedException;
use Rector\DocumentationGenerator\Command\DumpRectorsCommand;
use Rector\Utils\NodeDocumentationGenerator\Command\DumpNodesCommand;
use Symfony\Component\Console\Application as SymfonyApplication;
@ -20,6 +23,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symplify\PackageBuilder\Console\Command\CommandNaming;
use Symplify\SmartFileSystem\SmartFileInfo;
use Throwable;
final class Application extends SymfonyApplication
{
@ -33,11 +37,19 @@ final class Application extends SymfonyApplication
*/
private $configuration;
/**
* @var NoRectorsLoadedReporter
*/
private $noRectorsLoadedReporter;
/**
* @param Command[] $commands
*/
public function __construct(Configuration $configuration, array $commands = [])
{
public function __construct(
Configuration $configuration,
NoRectorsLoadedReporter $noRectorsLoadedReporter,
array $commands = []
) {
try {
$version = $configuration->getPrettyVersion();
} catch (OutOfBoundsException $outOfBoundsException) {
@ -48,6 +60,7 @@ final class Application extends SymfonyApplication
$this->addCommands($commands);
$this->configuration = $configuration;
$this->noRectorsLoadedReporter = $noRectorsLoadedReporter;
}
public function doRun(InputInterface $input, OutputInterface $output): int
@ -100,6 +113,17 @@ final class Application extends SymfonyApplication
return parent::doRun($input, $output);
}
public function renderThrowable(Throwable $throwable, OutputInterface $output): void
{
if (is_a($throwable, NoRectorsLoadedException::class)) {
$this->noRectorsLoadedReporter->report();
return;
}
// TODO: Change the autogenerated stub
parent::renderThrowable($throwable, $output);
}
protected function getDefaultInputDefinition(): InputDefinition
{
$defaultInputDefinition = parent::getDefaultInputDefinition();
@ -150,7 +174,7 @@ final class Application extends SymfonyApplication
private function addCustomOptions(InputDefinition $inputDefinition): void
{
$inputDefinition->addOption(new InputOption(
'config',
Option::OPTION_CONFIG,
'c',
InputOption::VALUE_REQUIRED,
'Path to config file',
@ -158,26 +182,33 @@ final class Application extends SymfonyApplication
));
$inputDefinition->addOption(new InputOption(
'set',
Option::OPTION_SET,
's',
InputOption::VALUE_REQUIRED,
'Finds config by shortcut name'
));
$inputDefinition->addOption(new InputOption(
'debug',
Option::OPTION_DEBUG,
null,
InputOption::VALUE_NONE,
'Enable debug verbosity (-vvv)'
));
$inputDefinition->addOption(new InputOption(
'xdebug',
Option::XDEBUG,
null,
InputOption::VALUE_NONE,
'Allow running xdebug'
));
$inputDefinition->addOption(new InputOption(
Option::OPTION_CLEAR_CACHE,
null,
InputOption::VALUE_NONE,
'Clear cache'
));
$inputDefinition->addOption(new InputOption(
'--working-dir',
'-d',

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\Core\Console\Command;
use Nette\Utils\Strings;
use Rector\Caching\Detector\ChangedFilesDetector;
use Rector\Core\Configuration\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Descriptor\TextDescriptor;
@ -15,6 +16,11 @@ use Symplify\PackageBuilder\Console\ShellCode;
abstract class AbstractCommand extends Command
{
/**
* @var ChangedFilesDetector
*/
protected $changedFilesDetector;
/**
* @var TextDescriptor
*/
@ -23,9 +29,12 @@ abstract class AbstractCommand extends Command
/**
* @required
*/
public function autowireAbstractCommand(TextDescriptor $textDescriptor): void
{
public function autowireAbstractCommand(
TextDescriptor $textDescriptor,
ChangedFilesDetector $changedFilesDetector
): void {
$this->textDescriptor = $textDescriptor;
$this->changedFilesDetector = $changedFilesDetector;
}
public function run(InputInterface $input, OutputInterface $output): int
@ -63,6 +72,14 @@ abstract class AbstractCommand extends Command
$this->getApplication()
->setCatchExceptions(false);
// clear cache
$this->changedFilesDetector->clear();
}
// clear cache
if ($input->getOption(Option::OPTION_CLEAR_CACHE)) {
$this->changedFilesDetector->clear();
}
}
}

View File

@ -86,11 +86,6 @@ final class ProcessCommand extends AbstractCommand
*/
private $unchangedFilesFilter;
/**
* @var ChangedFilesDetector
*/
private $changedFilesDetector;
/**
* @var SymfonyStyle
*/

View File

@ -25,12 +25,6 @@ final class RectorGuard
return;
}
// @todo @dx display nicer way instead of all red, as in https://github.com/symplify/symplify/blame/master/packages/easy-coding-standard/bin/ecs#L69-L83
throw new NoRectorsLoadedException(sprintf(
'We need some rectors to run:%s* register them in rector.php under "services():"%s* use "--set <set>"%s* or use "--config <file>.yaml"',
PHP_EOL,
PHP_EOL,
PHP_EOL
));
throw new NoRectorsLoadedException();
}
}

View File

@ -7,6 +7,7 @@ namespace Rector\Core\PHPStan\Reflection;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
@ -16,7 +17,10 @@ use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\Core\PHPStan\Reflection\TypeToCallReflectionResolver\TypeToCallReflectionResolverRegistry;
use Rector\Core\ValueObject\MethodName;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\NodeTypeResolver;
@ -55,6 +59,27 @@ final class CallReflectionResolver
$this->typeToCallReflectionResolverRegistry = $typeToCallReflectionResolverRegistry;
}
public function resolveConstructor(New_ $new): ?MethodReflection
{
/** @var Scope|null $scope */
$scope = $new->getAttribute(AttributeKey::SCOPE);
if ($scope === null) {
return null;
}
$classType = $this->nodeTypeResolver->resolve($new->class);
if ($classType instanceof UnionType) {
return $this->matchConstructorMethodInUnionType($classType, $scope);
}
if (! $classType->hasMethod(MethodName::CONSTRUCT)->yes()) {
return null;
}
return $classType->getMethod(MethodName::CONSTRUCT, $scope);
}
/**
* @param FuncCall|MethodCall|StaticCall $node
* @return MethodReflection|FunctionReflection|null
@ -70,7 +95,7 @@ final class CallReflectionResolver
/**
* @param FunctionReflection|MethodReflection|null $reflection
* @param FuncCall|MethodCall|StaticCall $node
* @param FuncCall|MethodCall|StaticCall|New_ $node
*/
public function resolveParametersAcceptor($reflection, Node $node): ?ParametersAcceptor
{
@ -98,6 +123,22 @@ final class CallReflectionResolver
return ParametersAcceptorSelector::selectFromArgs($scope, $node->args, $variants);
}
private function matchConstructorMethodInUnionType(UnionType $unionType, Scope $scope): ?MethodReflection
{
foreach ($unionType->getTypes() as $unionedType) {
if (! $unionedType instanceof TypeWithClassName) {
continue;
}
if (! $unionedType->hasMethod(MethodName::CONSTRUCT)->yes()) {
continue;
}
return $unionedType->getMethod(MethodName::CONSTRUCT, $scope);
}
return null;
}
/**
* @return FunctionReflection|MethodReflection|null
*/

View File

@ -11,17 +11,22 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\ClosureUse;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\List_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Foreach_;
use PHPStan\Reflection\ObjectTypeMethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Type\Type;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\Core\PHPStan\Reflection\CallReflectionResolver;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -57,13 +62,19 @@ final class ClassMethodAssignManipulator
*/
private $betterStandardPrinter;
/**
* @var CallReflectionResolver
*/
private $callReflectionResolver;
public function __construct(
BetterNodeFinder $betterNodeFinder,
BetterStandardPrinter $betterStandardPrinter,
CallableNodeTraverser $callableNodeTraverser,
NodeFactory $nodeFactory,
NodeNameResolver $nodeNameResolver,
VariableManipulator $variableManipulator
VariableManipulator $variableManipulator,
CallReflectionResolver $callReflectionResolver
) {
$this->variableManipulator = $variableManipulator;
$this->callableNodeTraverser = $callableNodeTraverser;
@ -71,6 +82,7 @@ final class ClassMethodAssignManipulator
$this->nodeFactory = $nodeFactory;
$this->betterNodeFinder = $betterNodeFinder;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->callReflectionResolver = $callReflectionResolver;
}
/**
@ -155,41 +167,7 @@ final class ClassMethodAssignManipulator
*/
private function filterOutReferencedVariables(array $variableAssigns, ClassMethod $classMethod): array
{
$referencedVariables = [];
$this->callableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) use (
&$referencedVariables
) {
if (! $node instanceof Variable) {
return null;
}
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode !== null && $this->isExplicitlyReferenced($parentNode)) {
/** @var string $variableName */
$variableName = $this->nodeNameResolver->getName($node);
$referencedVariables[] = $variableName;
return null;
}
if ($parentNode instanceof Arg) {
$parentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE);
}
if (! $parentNode instanceof FuncCall) {
return null;
}
if (! $this->nodeNameResolver->isNames($parentNode, ['array_shift', '*sort'])) {
return null;
}
if ($parentNode->args[0]->value === $node) {
/** @var string $variableName */
$variableName = $this->nodeNameResolver->getName($node);
$referencedVariables[] = $variableName;
}
});
$referencedVariables = $this->collectReferenceVariableNames($classMethod);
return array_filter($variableAssigns, function (Assign $assign) use ($referencedVariables): bool {
return ! $this->nodeNameResolver->isNames($assign->var, $referencedVariables);
@ -247,13 +225,55 @@ final class ClassMethodAssignManipulator
return false;
}
private function isExplicitlyReferenced(Node $node): bool
/**
* @return string[]
*/
private function collectReferenceVariableNames(ClassMethod $classMethod): array
{
if ($node instanceof Arg || $node instanceof ClosureUse || $node instanceof Param) {
return $node->byRef;
}
$referencedVariables = [];
return false;
$this->callableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) use (
&$referencedVariables
) {
if (! $node instanceof Variable) {
return null;
}
if ($this->nodeNameResolver->isName($node, 'this')) {
return null;
}
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode !== null && $this->isExplicitlyReferenced($parentNode)) {
/** @var string $variableName */
$variableName = $this->nodeNameResolver->getName($node);
$referencedVariables[] = $variableName;
return null;
}
$argumentPosition = null;
if ($parentNode instanceof Arg) {
$argumentPosition = $parentNode->getAttribute(AttributeKey::ARGUMENT_POSITION);
$parentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE);
}
if (! $parentNode instanceof Node) {
return null;
}
if ($argumentPosition === null) {
return null;
}
/** @var string $variableName */
$variableName = $this->nodeNameResolver->getName($node);
if ($this->isCallOrConstructorWithReference($parentNode, $node, $argumentPosition)) {
$referencedVariables[] = $variableName;
}
});
return $referencedVariables;
}
private function findParentForeach(Assign $assign): ?Foreach_
@ -266,4 +286,105 @@ final class ClassMethodAssignManipulator
return $foreach;
}
private function isExplicitlyReferenced(Node $node): bool
{
if ($node instanceof Arg || $node instanceof ClosureUse || $node instanceof Param) {
return $node->byRef;
}
return false;
}
private function isCallOrConstructorWithReference(Node $node, Variable $variable, int $argumentPosition): bool
{
if ($this->isMethodCallWithReferencedArgument($node, $variable)) {
return true;
}
if ($this->isFuncCallWithReferencedArgument($node, $variable)) {
return true;
}
return $this->isConstructorWithReference($node, $argumentPosition);
}
private function isMethodCallWithReferencedArgument(Node $node, Variable $variable): bool
{
if (! $node instanceof MethodCall) {
return false;
}
$methodReflection = $this->callReflectionResolver->resolveCall($node);
if (! $methodReflection instanceof ObjectTypeMethodReflection) {
return false;
}
$variableName = $this->nodeNameResolver->getName($variable);
$parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor($methodReflection, $node);
if ($parametersAcceptor === null) {
return false;
}
/** @var ParameterReflection $parameter */
foreach ($parametersAcceptor->getParameters() as $parameter) {
if ($parameter->getName() !== $variableName) {
continue;
}
return $parameter->passedByReference()
->yes();
}
return false;
}
/**
* Matches e.g:
* - array_shift($value)
* - sort($values)
*/
private function isFuncCallWithReferencedArgument(Node $node, Variable $variable): bool
{
if (! $node instanceof FuncCall) {
return false;
}
if (! $this->nodeNameResolver->isNames($node, ['array_shift', '*sort'])) {
return false;
}
// is 1t argument
return $node->args[0]->value !== $variable;
}
private function isConstructorWithReference(Node $node, int $argumentPosition): bool
{
if (! $node instanceof New_) {
return false;
}
return $this->isParameterReferencedInMethodReflection($node, $argumentPosition);
}
private function isParameterReferencedInMethodReflection(New_ $new, int $argumentPosition): bool
{
$methodReflection = $this->callReflectionResolver->resolveConstructor($new);
$parametersAcceptor = $this->callReflectionResolver->resolveParametersAcceptor($methodReflection, $new);
if ($parametersAcceptor === null) {
return false;
}
/** @var ParameterReflection $parameter */
foreach ($parametersAcceptor->getParameters() as $parameterPosition => $parameter) {
if ($parameterPosition !== $argumentPosition) {
continue;
}
return $parameter->passedByReference()
->yes();
}
return false;
}
}

22
travis.yml Normal file
View File

@ -0,0 +1,22 @@
# inspired by https://github.com/rectorphp/rector/blob/master/.travis.yml
language: php
dist: bionic
php: 7.4
before_install:
- phpenv config-rm xdebug.ini
jobs:
include:
-
name: "Split Monorepo"
if: (branch = master OR tag IS present) && type = push
script:
- composer update
# travis_retry to prevent fails
- travis_retry vendor/bin/monorepo-builder split --ansi
notifications:
email: false