mirror of
https://github.com/rectorphp/rector.git
synced 2025-04-20 15:31:58 +02:00
Updated Rector to commit 912d0b7ecc78aae4cbc2f3df7992fab9b5c63014
912d0b7ecc
[PhpParser] Remove regex check single line spaces before <?php on FileProcessor (#6525)
This commit is contained in:
parent
6ca3d0b122
commit
cce6781c15
@ -4,7 +4,6 @@ declare (strict_types=1);
|
||||
namespace Rector\Application;
|
||||
|
||||
use RectorPrefix202412\Nette\Utils\FileSystem;
|
||||
use RectorPrefix202412\Nette\Utils\Strings;
|
||||
use PHPStan\AnalysedCodeException;
|
||||
use PHPStan\Parser\ParserErrorsException;
|
||||
use Rector\Caching\Detector\ChangedFilesDetector;
|
||||
@ -23,7 +22,6 @@ use Rector\ValueObject\Application\File;
|
||||
use Rector\ValueObject\Configuration;
|
||||
use Rector\ValueObject\Error\SystemError;
|
||||
use Rector\ValueObject\FileProcessResult;
|
||||
use Rector\ValueObject\Reporting\FileDiff;
|
||||
use RectorPrefix202412\Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Throwable;
|
||||
final class FileProcessor
|
||||
@ -68,11 +66,6 @@ final class FileProcessor
|
||||
* @readonly
|
||||
*/
|
||||
private NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator;
|
||||
/**
|
||||
* @var string
|
||||
* @see https://regex101.com/r/llm7XZ/1
|
||||
*/
|
||||
private const OPEN_TAG_SPACED_REGEX = '#^[ \\t]+<\\?php#m';
|
||||
public function __construct(BetterStandardPrinter $betterStandardPrinter, RectorNodeTraverser $rectorNodeTraverser, SymfonyStyle $symfonyStyle, FileDiffFactory $fileDiffFactory, ChangedFilesDetector $changedFilesDetector, ErrorFactory $errorFactory, FilePathHelper $filePathHelper, PostFileProcessor $postFileProcessor, RectorParser $rectorParser, NodeScopeAndMetadataDecorator $nodeScopeAndMetadataDecorator)
|
||||
{
|
||||
$this->betterStandardPrinter = $betterStandardPrinter;
|
||||
@ -157,26 +150,6 @@ final class FileProcessor
|
||||
{
|
||||
// only save to string first, no need to print to file when not needed
|
||||
$newContent = $this->betterStandardPrinter->printFormatPreserving($file->getNewStmts(), $file->getOldStmts(), $file->getOldTokens());
|
||||
/**
|
||||
* When no diff applied, the PostRector may still change the content, that's why printing still needed
|
||||
* On printing, the space may be wiped, these below check compare with original file content used to verify
|
||||
* that no change actually needed
|
||||
*/
|
||||
if (!$file->getFileDiff() instanceof FileDiff) {
|
||||
/**
|
||||
* exact compare with original file content
|
||||
*/
|
||||
$originalFileContent = $file->getOriginalFileContent();
|
||||
if ($originalFileContent === $newContent) {
|
||||
return;
|
||||
}
|
||||
// handle space before <?php
|
||||
$strippedNewContent = Strings::replace($newContent, self::OPEN_TAG_SPACED_REGEX, '<?php');
|
||||
$strippedOriginalFileContent = Strings::replace($originalFileContent, self::OPEN_TAG_SPACED_REGEX, '<?php');
|
||||
if ($strippedOriginalFileContent === $strippedNewContent) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// change file content early to make $file->hasChanged() based on new content
|
||||
$file->changeFileContent($newContent);
|
||||
if ($configuration->isDryRun()) {
|
||||
|
@ -19,12 +19,12 @@ final class VersionResolver
|
||||
* @api
|
||||
* @var string
|
||||
*/
|
||||
public const PACKAGE_VERSION = '1b68ff48b80d81bd4295ef304196fd97d74e0032';
|
||||
public const PACKAGE_VERSION = '912d0b7ecc78aae4cbc2f3df7992fab9b5c63014';
|
||||
/**
|
||||
* @api
|
||||
* @var string
|
||||
*/
|
||||
public const RELEASE_DATE = '2024-12-01 00:40:46';
|
||||
public const RELEASE_DATE = '2024-12-01 22:00:05';
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
|
@ -22,6 +22,7 @@ use PhpParser\Node\Scalar\Int_;
|
||||
use PhpParser\Node\Scalar\InterpolatedString;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Declare_;
|
||||
use PhpParser\Node\Stmt\InlineHTML;
|
||||
use PhpParser\Node\Stmt\Nop;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
use PHPStan\Node\Expr\AlwaysRememberedExpr;
|
||||
@ -343,12 +344,18 @@ final class BetterStandardPrinter extends Standard
|
||||
*/
|
||||
private function containsNop(array $nodes) : bool
|
||||
{
|
||||
$hasNop = \false;
|
||||
foreach ($nodes as $node) {
|
||||
// early false when visited Node is InlineHTML
|
||||
if ($node instanceof InlineHTML) {
|
||||
return \false;
|
||||
}
|
||||
// use flag to avoid next is InlineHTML that returns early
|
||||
if ($node instanceof Nop) {
|
||||
return \true;
|
||||
$hasNop = \true;
|
||||
}
|
||||
}
|
||||
return \false;
|
||||
return $hasNop;
|
||||
}
|
||||
private function wrapValueWith(String_ $string, string $wrap) : string
|
||||
{
|
||||
|
3
vendor/composer/autoload_classmap.php
vendored
3
vendor/composer/autoload_classmap.php
vendored
@ -2236,6 +2236,9 @@ return array(
|
||||
'Rector\\Symfony\\Contract\\Tag\\TagInterface' => $vendorDir . '/rector/rector-symfony/src/Contract/Tag/TagInterface.php',
|
||||
'Rector\\Symfony\\DataProvider\\ServiceMapProvider' => $vendorDir . '/rector/rector-symfony/src/DataProvider/ServiceMapProvider.php',
|
||||
'Rector\\Symfony\\DataProvider\\ServiceNameToTypeUniqueProvider' => $vendorDir . '/rector/rector-symfony/src/DataProvider/ServiceNameToTypeUniqueProvider.php',
|
||||
'Rector\\Symfony\\DependencyInjection\\NodeDecorator\\CommandConstructorDecorator' => $vendorDir . '/rector/rector-symfony/rules/DependencyInjection/NodeDecorator/CommandConstructorDecorator.php',
|
||||
'Rector\\Symfony\\DependencyInjection\\Rector\\Class_\\CommandGetByTypeToConstructorInjectionRector' => $vendorDir . '/rector/rector-symfony/rules/DependencyInjection/Rector/Class_/CommandGetByTypeToConstructorInjectionRector.php',
|
||||
'Rector\\Symfony\\DependencyInjection\\Rector\\Class_\\ControllerGetByTypeToConstructorInjectionRector' => $vendorDir . '/rector/rector-symfony/rules/DependencyInjection/Rector/Class_/ControllerGetByTypeToConstructorInjectionRector.php',
|
||||
'Rector\\Symfony\\DowngradeSymfony70\\Rector\\Class_\\DowngradeSymfonyCommandAttributeRector' => $vendorDir . '/rector/rector-symfony/rules/DowngradeSymfony70/Rector/Class_/DowngradeSymfonyCommandAttributeRector.php',
|
||||
'Rector\\Symfony\\Enum\\SensioAttribute' => $vendorDir . '/rector/rector-symfony/src/Enum/SensioAttribute.php',
|
||||
'Rector\\Symfony\\Enum\\SymfonyAnnotation' => $vendorDir . '/rector/rector-symfony/src/Enum/SymfonyAnnotation.php',
|
||||
|
3
vendor/composer/autoload_static.php
vendored
3
vendor/composer/autoload_static.php
vendored
@ -2455,6 +2455,9 @@ class ComposerStaticInitc12d7e0a7ec6f5f877903ca571cd9ab7
|
||||
'Rector\\Symfony\\Contract\\Tag\\TagInterface' => __DIR__ . '/..' . '/rector/rector-symfony/src/Contract/Tag/TagInterface.php',
|
||||
'Rector\\Symfony\\DataProvider\\ServiceMapProvider' => __DIR__ . '/..' . '/rector/rector-symfony/src/DataProvider/ServiceMapProvider.php',
|
||||
'Rector\\Symfony\\DataProvider\\ServiceNameToTypeUniqueProvider' => __DIR__ . '/..' . '/rector/rector-symfony/src/DataProvider/ServiceNameToTypeUniqueProvider.php',
|
||||
'Rector\\Symfony\\DependencyInjection\\NodeDecorator\\CommandConstructorDecorator' => __DIR__ . '/..' . '/rector/rector-symfony/rules/DependencyInjection/NodeDecorator/CommandConstructorDecorator.php',
|
||||
'Rector\\Symfony\\DependencyInjection\\Rector\\Class_\\CommandGetByTypeToConstructorInjectionRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/DependencyInjection/Rector/Class_/CommandGetByTypeToConstructorInjectionRector.php',
|
||||
'Rector\\Symfony\\DependencyInjection\\Rector\\Class_\\ControllerGetByTypeToConstructorInjectionRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/DependencyInjection/Rector/Class_/ControllerGetByTypeToConstructorInjectionRector.php',
|
||||
'Rector\\Symfony\\DowngradeSymfony70\\Rector\\Class_\\DowngradeSymfonyCommandAttributeRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/DowngradeSymfony70/Rector/Class_/DowngradeSymfonyCommandAttributeRector.php',
|
||||
'Rector\\Symfony\\Enum\\SensioAttribute' => __DIR__ . '/..' . '/rector/rector-symfony/src/Enum/SensioAttribute.php',
|
||||
'Rector\\Symfony\\Enum\\SymfonyAnnotation' => __DIR__ . '/..' . '/rector/rector-symfony/src/Enum/SymfonyAnnotation.php',
|
||||
|
8
vendor/composer/installed.json
vendored
8
vendor/composer/installed.json
vendored
@ -1868,12 +1868,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https:\/\/github.com\/rectorphp\/rector-symfony.git",
|
||||
"reference": "dfec6fd94c2cc2dc866b18c152aa7ecd45fd828e"
|
||||
"reference": "617b3aff39d3ae14e5ea24ad5150fb4d0fc4a05e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-symfony\/zipball\/dfec6fd94c2cc2dc866b18c152aa7ecd45fd828e",
|
||||
"reference": "dfec6fd94c2cc2dc866b18c152aa7ecd45fd828e",
|
||||
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-symfony\/zipball\/617b3aff39d3ae14e5ea24ad5150fb4d0fc4a05e",
|
||||
"reference": "617b3aff39d3ae14e5ea24ad5150fb4d0fc4a05e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1898,7 +1898,7 @@
|
||||
"tomasvotruba\/class-leak": "^1.0",
|
||||
"tracy\/tracy": "^2.10"
|
||||
},
|
||||
"time": "2024-11-24T16:11:00+00:00",
|
||||
"time": "2024-12-01T14:07:35+00:00",
|
||||
"default-branch": true,
|
||||
"type": "rector-extension",
|
||||
"extra": {
|
||||
|
2
vendor/composer/installed.php
vendored
2
vendor/composer/installed.php
vendored
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@ namespace Rector\RectorInstaller;
|
||||
*/
|
||||
final class GeneratedConfig
|
||||
{
|
||||
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 02ae401'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main 7bb52fb'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main a506b2c'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main dfec6fd'));
|
||||
public const EXTENSIONS = array('rector/rector-doctrine' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-doctrine', 'relative_install_path' => '../../rector-doctrine', 'extra' => NULL, 'version' => 'dev-main 02ae401'), 'rector/rector-downgrade-php' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-downgrade-php', 'relative_install_path' => '../../rector-downgrade-php', 'extra' => NULL, 'version' => 'dev-main 7bb52fb'), 'rector/rector-phpunit' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-phpunit', 'relative_install_path' => '../../rector-phpunit', 'extra' => NULL, 'version' => 'dev-main a506b2c'), 'rector/rector-symfony' => array('install_path' => '/home/runner/work/rector-src/rector-src/rector-build/vendor/rector/rector-symfony', 'relative_install_path' => '../../rector-symfony', 'extra' => NULL, 'version' => 'dev-main 617b3af'));
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
@ -8,5 +8,13 @@ use Rector\Symfony\Symfony28\Rector\MethodCall\GetToConstructorInjectionRector;
|
||||
use Rector\Symfony\Symfony34\Rector\Closure\ContainerGetNameToTypeInTestsRector;
|
||||
use Rector\Symfony\Symfony42\Rector\MethodCall\ContainerGetToConstructorInjectionRector;
|
||||
return static function (RectorConfig $rectorConfig) : void {
|
||||
$rectorConfig->rules([ContainerGetToConstructorInjectionRector::class, ContainerGetNameToTypeInTestsRector::class, GetToConstructorInjectionRector::class]);
|
||||
$rectorConfig->rules([
|
||||
// modern step-by-step narrow approach
|
||||
\Rector\Symfony\DependencyInjection\Rector\Class_\ControllerGetByTypeToConstructorInjectionRector::class,
|
||||
\Rector\Symfony\DependencyInjection\Rector\Class_\CommandGetByTypeToConstructorInjectionRector::class,
|
||||
// legacy rules that require container fetch
|
||||
ContainerGetToConstructorInjectionRector::class,
|
||||
ContainerGetNameToTypeInTestsRector::class,
|
||||
GetToConstructorInjectionRector::class,
|
||||
]);
|
||||
};
|
||||
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace Rector\Symfony\DependencyInjection\NodeDecorator;
|
||||
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use Rector\NodeTypeResolver\NodeTypeResolver;
|
||||
use Rector\ValueObject\MethodName;
|
||||
final class CommandConstructorDecorator
|
||||
{
|
||||
private NodeTypeResolver $nodeTypeResolver;
|
||||
public function __construct(NodeTypeResolver $nodeTypeResolver)
|
||||
{
|
||||
$this->nodeTypeResolver = $nodeTypeResolver;
|
||||
}
|
||||
public function decorate(Class_ $class) : void
|
||||
{
|
||||
// special case for command to keep parent constructor call
|
||||
if (!$this->nodeTypeResolver->isObjectType($class, new ObjectType('Symfony\\Component\\Console\\Command\\Command'))) {
|
||||
return;
|
||||
}
|
||||
$constuctClassMethod = $class->getMethod(MethodName::CONSTRUCT);
|
||||
if (!$constuctClassMethod instanceof ClassMethod) {
|
||||
return;
|
||||
}
|
||||
// empty stmts? add parent::__construct() to setup command
|
||||
if ((array) $constuctClassMethod->stmts === []) {
|
||||
$parentConstructStaticCall = new StaticCall(new Name('parent'), '__construct');
|
||||
$constuctClassMethod->stmts[] = new Expression($parentConstructStaticCall);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace Rector\Symfony\DependencyInjection\Rector\Class_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use Rector\Naming\Naming\PropertyNaming;
|
||||
use Rector\NodeManipulator\ClassDependencyManipulator;
|
||||
use Rector\PHPStan\ScopeFetcher;
|
||||
use Rector\PostRector\ValueObject\PropertyMetadata;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
|
||||
use Rector\Symfony\DependencyInjection\NodeDecorator\CommandConstructorDecorator;
|
||||
use Rector\Symfony\Enum\SymfonyClass;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
/**
|
||||
* @see \Rector\Symfony\Tests\DependencyInjection\Rector\Class_\CommandGetByTypeToConstructorInjectionRector\CommandGetByTypeToConstructorInjectionRectorTest
|
||||
*/
|
||||
final class CommandGetByTypeToConstructorInjectionRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
private ClassDependencyManipulator $classDependencyManipulator;
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
private PropertyNaming $propertyNaming;
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
private CommandConstructorDecorator $commandConstructorDecorator;
|
||||
public function __construct(ClassDependencyManipulator $classDependencyManipulator, PropertyNaming $propertyNaming, CommandConstructorDecorator $commandConstructorDecorator)
|
||||
{
|
||||
$this->classDependencyManipulator = $classDependencyManipulator;
|
||||
$this->propertyNaming = $propertyNaming;
|
||||
$this->commandConstructorDecorator = $commandConstructorDecorator;
|
||||
}
|
||||
public function getRuleDefinition() : RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('From `$container->get(SomeType::class)` in commands to constructor injection (step 2/x)', [new CodeSample(<<<'CODE_SAMPLE'
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
|
||||
final class SomeCommand extends ContainerAwareCommand
|
||||
{
|
||||
public function someMethod()
|
||||
{
|
||||
$someType = $this->get(SomeType::class);
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
, <<<'CODE_SAMPLE'
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
|
||||
final class SomeCommand extends ContainerAwareCommand
|
||||
{
|
||||
public function __construct(private SomeType $someType)
|
||||
{
|
||||
}
|
||||
|
||||
public function someMethod()
|
||||
{
|
||||
$someType = $this->someType;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
)]);
|
||||
}
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes() : array
|
||||
{
|
||||
return [Class_::class];
|
||||
}
|
||||
/**
|
||||
* @param Class_ $node
|
||||
*/
|
||||
public function refactor(Node $node) : ?Node
|
||||
{
|
||||
if ($this->shouldSkipClass($node)) {
|
||||
return null;
|
||||
}
|
||||
$propertyMetadatas = [];
|
||||
$this->traverseNodesWithCallable($node, function (Node $node) use(&$propertyMetadatas) : ?Node {
|
||||
if (!$node instanceof MethodCall) {
|
||||
return null;
|
||||
}
|
||||
if ($node->isFirstClassCallable()) {
|
||||
return null;
|
||||
}
|
||||
if (!$this->isName($node->name, 'get')) {
|
||||
return null;
|
||||
}
|
||||
if (!$this->isName($node->var, 'this')) {
|
||||
return null;
|
||||
}
|
||||
if (\count($node->getArgs()) !== 1) {
|
||||
return null;
|
||||
}
|
||||
$firstArg = $node->getArgs()[0];
|
||||
if (!$firstArg->value instanceof ClassConstFetch) {
|
||||
return null;
|
||||
}
|
||||
// must be class const fetch
|
||||
if (!$this->isName($firstArg->value->name, 'class')) {
|
||||
return null;
|
||||
}
|
||||
$className = $this->getName($firstArg->value->class);
|
||||
if (!\is_string($className)) {
|
||||
return null;
|
||||
}
|
||||
$propertyName = $this->propertyNaming->fqnToVariableName($className);
|
||||
$propertyMetadata = new PropertyMetadata($propertyName, new FullyQualifiedObjectType($className));
|
||||
$propertyMetadatas[] = $propertyMetadata;
|
||||
return $this->nodeFactory->createPropertyFetch('this', $propertyMetadata->getName());
|
||||
});
|
||||
if ($propertyMetadatas === []) {
|
||||
return null;
|
||||
}
|
||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||
$this->classDependencyManipulator->addConstructorDependency($node, $propertyMetadata);
|
||||
}
|
||||
$this->commandConstructorDecorator->decorate($node);
|
||||
return $node;
|
||||
}
|
||||
private function shouldSkipClass(Class_ $class) : bool
|
||||
{
|
||||
// keep it safe
|
||||
if ($class->isAbstract()) {
|
||||
return \true;
|
||||
}
|
||||
$scope = ScopeFetcher::fetch($class);
|
||||
$classReflection = $scope->getClassReflection();
|
||||
if (!$classReflection instanceof ClassReflection) {
|
||||
return \true;
|
||||
}
|
||||
return !$classReflection->isSubclassOf(SymfonyClass::CONTAINER_AWARE_COMMAND);
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare (strict_types=1);
|
||||
namespace Rector\Symfony\DependencyInjection\Rector\Class_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use Rector\Naming\Naming\PropertyNaming;
|
||||
use Rector\NodeManipulator\ClassDependencyManipulator;
|
||||
use Rector\PHPStan\ScopeFetcher;
|
||||
use Rector\PostRector\ValueObject\PropertyMetadata;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
|
||||
use Rector\Symfony\Enum\SymfonyClass;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
/**
|
||||
* @see \Rector\Symfony\Tests\DependencyInjection\Rector\Class_\ControllerGetByTypeToConstructorInjectionRector\ControllerGetByTypeToConstructorInjectionRectorTest
|
||||
*/
|
||||
final class ControllerGetByTypeToConstructorInjectionRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
private ClassDependencyManipulator $classDependencyManipulator;
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
private PropertyNaming $propertyNaming;
|
||||
public function __construct(ClassDependencyManipulator $classDependencyManipulator, PropertyNaming $propertyNaming)
|
||||
{
|
||||
$this->classDependencyManipulator = $classDependencyManipulator;
|
||||
$this->propertyNaming = $propertyNaming;
|
||||
}
|
||||
public function getRuleDefinition() : RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('From `$container->get(SomeType::class)` in controllers to constructor injection (step 1/x)', [new CodeSample(<<<'CODE_SAMPLE'
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
|
||||
final class SomeCommand extends Controller
|
||||
{
|
||||
public function someMethod()
|
||||
{
|
||||
$someType = $this->get(SomeType::class);
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
, <<<'CODE_SAMPLE'
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
|
||||
final class SomeCommand extends Controller
|
||||
{
|
||||
public function __construct(private SomeType $someType)
|
||||
{
|
||||
}
|
||||
|
||||
public function someMethod()
|
||||
{
|
||||
$someType = $this->someType;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
)]);
|
||||
}
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes() : array
|
||||
{
|
||||
return [Class_::class];
|
||||
}
|
||||
/**
|
||||
* @param Class_ $node
|
||||
*/
|
||||
public function refactor(Node $node) : ?Node
|
||||
{
|
||||
if ($this->shouldSkipClass($node)) {
|
||||
return null;
|
||||
}
|
||||
$propertyMetadatas = [];
|
||||
$this->traverseNodesWithCallable($node, function (Node $node) use(&$propertyMetadatas) : ?Node {
|
||||
if (!$node instanceof MethodCall) {
|
||||
return null;
|
||||
}
|
||||
if ($node->isFirstClassCallable()) {
|
||||
return null;
|
||||
}
|
||||
if (!$this->isName($node->name, 'get')) {
|
||||
return null;
|
||||
}
|
||||
if (!$this->isName($node->var, 'this')) {
|
||||
return null;
|
||||
}
|
||||
if (\count($node->getArgs()) !== 1) {
|
||||
return null;
|
||||
}
|
||||
$firstArg = $node->getArgs()[0];
|
||||
if (!$firstArg->value instanceof ClassConstFetch) {
|
||||
return null;
|
||||
}
|
||||
// must be class const fetch
|
||||
if (!$this->isName($firstArg->value->name, 'class')) {
|
||||
return null;
|
||||
}
|
||||
$className = $this->getName($firstArg->value->class);
|
||||
if (!\is_string($className)) {
|
||||
return null;
|
||||
}
|
||||
$propertyName = $this->propertyNaming->fqnToVariableName($className);
|
||||
$propertyMetadata = new PropertyMetadata($propertyName, new FullyQualifiedObjectType($className));
|
||||
$propertyMetadatas[] = $propertyMetadata;
|
||||
return $this->nodeFactory->createPropertyFetch('this', $propertyMetadata->getName());
|
||||
});
|
||||
if ($propertyMetadatas === []) {
|
||||
return null;
|
||||
}
|
||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||
$this->classDependencyManipulator->addConstructorDependency($node, $propertyMetadata);
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
private function shouldSkipClass(Class_ $class) : bool
|
||||
{
|
||||
// keep it safe
|
||||
if (!$class->isFinal()) {
|
||||
return \true;
|
||||
}
|
||||
$scope = ScopeFetcher::fetch($class);
|
||||
$classReflection = $scope->getClassReflection();
|
||||
if (!$classReflection instanceof ClassReflection) {
|
||||
return \true;
|
||||
}
|
||||
return !$classReflection->isSubclassOf(SymfonyClass::CONTROLLER);
|
||||
}
|
||||
}
|
@ -6,18 +6,14 @@ namespace Rector\Symfony\Symfony42\Rector\MethodCall;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PHPStan\Type\ObjectType;
|
||||
use Rector\NodeManipulator\ClassDependencyManipulator;
|
||||
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer;
|
||||
use Rector\PostRector\ValueObject\PropertyMetadata;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\Symfony\DependencyInjection\NodeDecorator\CommandConstructorDecorator;
|
||||
use Rector\Symfony\NodeAnalyzer\DependencyInjectionMethodCallAnalyzer;
|
||||
use Rector\ValueObject\MethodName;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
/**
|
||||
@ -39,11 +35,16 @@ final class ContainerGetToConstructorInjectionRector extends AbstractRector
|
||||
* @readonly
|
||||
*/
|
||||
private ClassDependencyManipulator $classDependencyManipulator;
|
||||
public function __construct(DependencyInjectionMethodCallAnalyzer $dependencyInjectionMethodCallAnalyzer, TestsNodeAnalyzer $testsNodeAnalyzer, ClassDependencyManipulator $classDependencyManipulator)
|
||||
/**
|
||||
* @readonly
|
||||
*/
|
||||
private CommandConstructorDecorator $commandConstructorDecorator;
|
||||
public function __construct(DependencyInjectionMethodCallAnalyzer $dependencyInjectionMethodCallAnalyzer, TestsNodeAnalyzer $testsNodeAnalyzer, ClassDependencyManipulator $classDependencyManipulator, CommandConstructorDecorator $commandConstructorDecorator)
|
||||
{
|
||||
$this->dependencyInjectionMethodCallAnalyzer = $dependencyInjectionMethodCallAnalyzer;
|
||||
$this->testsNodeAnalyzer = $testsNodeAnalyzer;
|
||||
$this->classDependencyManipulator = $classDependencyManipulator;
|
||||
$this->commandConstructorDecorator = $commandConstructorDecorator;
|
||||
}
|
||||
public function getRuleDefinition() : RuleDefinition
|
||||
{
|
||||
@ -123,23 +124,7 @@ CODE_SAMPLE
|
||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||
$this->classDependencyManipulator->addConstructorDependency($class, $propertyMetadata);
|
||||
}
|
||||
$this->decorateCommandConstructor($class);
|
||||
$this->commandConstructorDecorator->decorate($class);
|
||||
return $node;
|
||||
}
|
||||
private function decorateCommandConstructor(Class_ $class) : void
|
||||
{
|
||||
// special case for command to keep parent constructor call
|
||||
if (!$this->isObjectType($class, new ObjectType('Symfony\\Component\\Console\\Command\\Command'))) {
|
||||
return;
|
||||
}
|
||||
$constuctClassMethod = $class->getMethod(MethodName::CONSTRUCT);
|
||||
if (!$constuctClassMethod instanceof ClassMethod) {
|
||||
return;
|
||||
}
|
||||
// empty stmts? add parent::__construct() to setup command
|
||||
if ((array) $constuctClassMethod->stmts === []) {
|
||||
$parentConstructStaticCall = new StaticCall(new Name('parent'), '__construct');
|
||||
$constuctClassMethod->stmts[] = new Expression($parentConstructStaticCall);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,10 @@ namespace Rector\Symfony\Enum;
|
||||
|
||||
final class SymfonyClass
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CONTROLLER = 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -13,4 +17,8 @@ final class SymfonyClass
|
||||
* @var string
|
||||
*/
|
||||
public const COMMAND = 'Symfony\\Component\\Console\\Command\\Command';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CONTAINER_AWARE_COMMAND = 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand';
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ final class ServiceTypeMethodCallResolver
|
||||
if (!isset($methodCall->args[0])) {
|
||||
return new MixedType();
|
||||
}
|
||||
$argument = $methodCall->getArgs()[0]->value;
|
||||
$firstArg = $methodCall->getArgs()[0];
|
||||
$argument = $firstArg->value;
|
||||
$serviceMap = $this->serviceMapProvider->provide();
|
||||
if ($argument instanceof String_) {
|
||||
return $serviceMap->getServiceType($argument->value);
|
||||
|
Loading…
x
Reference in New Issue
Block a user