Updated Rector to commit 3fe55534ea6c60a09d5ab1de5573f9b907a55bcf

3fe55534ea  [DeadCode] Skip fluent no return type on RemoveUnusedPrivateMethodRector take 2 (#6116)
This commit is contained in:
Tomas Votruba 2024-07-04 13:07:32 +00:00
parent 2720c63eec
commit b845779e98
19 changed files with 565 additions and 24 deletions

View File

@ -6,10 +6,10 @@ namespace Rector\DeadCode\NodeAnalyzer;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\NullsafeMethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PHPStan\Type\MixedType;
use PHPStan\Type\ThisType;
use PHPStan\Type\TypeWithClassName;
use Rector\Enum\ObjectReference;
use Rector\NodeNameResolver\NodeNameResolver;
@ -44,12 +44,21 @@ final class CallCollectionAnalyzer
// that methods don't have return type
if ($callerType instanceof MixedType && !$callerType->isExplicitMixed()) {
$cloneCallerRoot = clone $callerRoot;
while ($cloneCallerRoot instanceof MethodCall && $cloneCallerRoot->var instanceof MethodCall) {
$callerType = $this->nodeTypeResolver->getType($cloneCallerRoot->var->var);
$cloneCallerRoot = $cloneCallerRoot->var;
if ($callerType instanceof ThisType && $callerType->getStaticObjectType()->getClassName() === $className) {
return \true;
$isFluent = \false;
// init
$methodCallNames = [];
// first append
$methodCallNames[] = (string) $this->nodeNameResolver->getName($call->name);
while ($cloneCallerRoot instanceof MethodCall) {
$methodCallNames[] = (string) $this->nodeNameResolver->getName($cloneCallerRoot->name);
if ($cloneCallerRoot->var instanceof Variable && $cloneCallerRoot->var->name === 'this') {
$isFluent = \true;
break;
}
$cloneCallerRoot = $cloneCallerRoot->var;
}
if ($isFluent && \in_array($classMethodName, $methodCallNames, \true)) {
return \true;
}
}
continue;

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = '5fe7f4e12add95d0cb6d476ea9f4776492b38ed3';
public const PACKAGE_VERSION = '3fe55534ea6c60a09d5ab1de5573f9b907a55bcf';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2024-07-04 01:09:26';
public const RELEASE_DATE = '2024-07-04 20:05:11';
/**
* @var int
*/

View File

@ -2164,12 +2164,17 @@ return array(
'Rector\\Symfony\\Configs\\Enum\\DoctrineConfigKey' => $vendorDir . '/rector/rector-symfony/rules/Configs/Enum/DoctrineConfigKey.php',
'Rector\\Symfony\\Configs\\Enum\\GroupingMethods' => $vendorDir . '/rector/rector-symfony/rules/Configs/Enum/GroupingMethods.php',
'Rector\\Symfony\\Configs\\Enum\\SecurityConfigKey' => $vendorDir . '/rector/rector-symfony/rules/Configs/Enum/SecurityConfigKey.php',
'Rector\\Symfony\\Configs\\NodeAnalyser\\ConfigServiceArgumentsResolver' => $vendorDir . '/rector/rector-symfony/rules/Configs/NodeAnalyser/ConfigServiceArgumentsResolver.php',
'Rector\\Symfony\\Configs\\NodeAnalyser\\SetServiceClassNameResolver' => $vendorDir . '/rector/rector-symfony/rules/Configs/NodeAnalyser/SetServiceClassNameResolver.php',
'Rector\\Symfony\\Configs\\NodeVisitor\\CollectServiceArgumentsNodeVisitor' => $vendorDir . '/rector/rector-symfony/rules/Configs/NodeVisitor/CollectServiceArgumentsNodeVisitor.php',
'Rector\\Symfony\\Configs\\Rector\\ClassMethod\\AddRouteAnnotationRector' => $vendorDir . '/rector/rector-symfony/rules/Configs/Rector/ClassMethod/AddRouteAnnotationRector.php',
'Rector\\Symfony\\Configs\\Rector\\Class_\\AutowireAttributeRector' => $vendorDir . '/rector/rector-symfony/rules/Configs/Rector/Class_/AutowireAttributeRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceArgsToServiceNamedArgRector' => $vendorDir . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceArgsToServiceNamedArgRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceSetStringNameToClassNameRector' => $vendorDir . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceSetStringNameToClassNameRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceSettersToSettersAutodiscoveryRector' => $vendorDir . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceSettersToSettersAutodiscoveryRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceTagsToDefaultsAutoconfigureRector' => $vendorDir . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceTagsToDefaultsAutoconfigureRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServicesSetNameToSetTypeRector' => $vendorDir . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServicesSetNameToSetTypeRector.php',
'Rector\\Symfony\\Configs\\ValueObject\\ServiceArguments' => $vendorDir . '/rector/rector-symfony/rules/Configs/ValueObject/ServiceArguments.php',
'Rector\\Symfony\\Contract\\Bridge\\Symfony\\Routing\\SymfonyRoutesProviderInterface' => $vendorDir . '/rector/rector-symfony/src/Contract/Bridge/Symfony/Routing/SymfonyRoutesProviderInterface.php',
'Rector\\Symfony\\Contract\\EventReferenceToMethodNameInterface' => $vendorDir . '/rector/rector-symfony/src/Contract/EventReferenceToMethodNameInterface.php',
'Rector\\Symfony\\Contract\\Tag\\TagInterface' => $vendorDir . '/rector/rector-symfony/src/Contract/Tag/TagInterface.php',
@ -2217,6 +2222,7 @@ return array(
'Rector\\Symfony\\NodeManipulator\\ArrayManipulator' => $vendorDir . '/rector/rector-symfony/src/NodeManipulator/ArrayManipulator.php',
'Rector\\Symfony\\NodeManipulator\\ClassManipulator' => $vendorDir . '/rector/rector-symfony/src/NodeManipulator/ClassManipulator.php',
'Rector\\Symfony\\PhpDocNode\\SymfonyRouteTagValueNodeFactory' => $vendorDir . '/rector/rector-symfony/src/PhpDocNode/SymfonyRouteTagValueNodeFactory.php',
'Rector\\Symfony\\PhpParser\\NamedSimplePhpParser' => $vendorDir . '/rector/rector-symfony/src/PhpParser/NamedSimplePhpParser.php',
'Rector\\Symfony\\Set\\FOSRestSetList' => $vendorDir . '/rector/rector-symfony/src/Set/FOSRestSetList.php',
'Rector\\Symfony\\Set\\JMSSetList' => $vendorDir . '/rector/rector-symfony/src/Set/JMSSetList.php',
'Rector\\Symfony\\Set\\SensiolabsSetList' => $vendorDir . '/rector/rector-symfony/src/Set/SensiolabsSetList.php',

View File

@ -2383,12 +2383,17 @@ class ComposerStaticInit2971ea952332f12bcefd9fbb21b5c118
'Rector\\Symfony\\Configs\\Enum\\DoctrineConfigKey' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Enum/DoctrineConfigKey.php',
'Rector\\Symfony\\Configs\\Enum\\GroupingMethods' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Enum/GroupingMethods.php',
'Rector\\Symfony\\Configs\\Enum\\SecurityConfigKey' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Enum/SecurityConfigKey.php',
'Rector\\Symfony\\Configs\\NodeAnalyser\\ConfigServiceArgumentsResolver' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/NodeAnalyser/ConfigServiceArgumentsResolver.php',
'Rector\\Symfony\\Configs\\NodeAnalyser\\SetServiceClassNameResolver' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/NodeAnalyser/SetServiceClassNameResolver.php',
'Rector\\Symfony\\Configs\\NodeVisitor\\CollectServiceArgumentsNodeVisitor' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/NodeVisitor/CollectServiceArgumentsNodeVisitor.php',
'Rector\\Symfony\\Configs\\Rector\\ClassMethod\\AddRouteAnnotationRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Rector/ClassMethod/AddRouteAnnotationRector.php',
'Rector\\Symfony\\Configs\\Rector\\Class_\\AutowireAttributeRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Rector/Class_/AutowireAttributeRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceArgsToServiceNamedArgRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceArgsToServiceNamedArgRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceSetStringNameToClassNameRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceSetStringNameToClassNameRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceSettersToSettersAutodiscoveryRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceSettersToSettersAutodiscoveryRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServiceTagsToDefaultsAutoconfigureRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServiceTagsToDefaultsAutoconfigureRector.php',
'Rector\\Symfony\\Configs\\Rector\\Closure\\ServicesSetNameToSetTypeRector' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/Rector/Closure/ServicesSetNameToSetTypeRector.php',
'Rector\\Symfony\\Configs\\ValueObject\\ServiceArguments' => __DIR__ . '/..' . '/rector/rector-symfony/rules/Configs/ValueObject/ServiceArguments.php',
'Rector\\Symfony\\Contract\\Bridge\\Symfony\\Routing\\SymfonyRoutesProviderInterface' => __DIR__ . '/..' . '/rector/rector-symfony/src/Contract/Bridge/Symfony/Routing/SymfonyRoutesProviderInterface.php',
'Rector\\Symfony\\Contract\\EventReferenceToMethodNameInterface' => __DIR__ . '/..' . '/rector/rector-symfony/src/Contract/EventReferenceToMethodNameInterface.php',
'Rector\\Symfony\\Contract\\Tag\\TagInterface' => __DIR__ . '/..' . '/rector/rector-symfony/src/Contract/Tag/TagInterface.php',
@ -2436,6 +2441,7 @@ class ComposerStaticInit2971ea952332f12bcefd9fbb21b5c118
'Rector\\Symfony\\NodeManipulator\\ArrayManipulator' => __DIR__ . '/..' . '/rector/rector-symfony/src/NodeManipulator/ArrayManipulator.php',
'Rector\\Symfony\\NodeManipulator\\ClassManipulator' => __DIR__ . '/..' . '/rector/rector-symfony/src/NodeManipulator/ClassManipulator.php',
'Rector\\Symfony\\PhpDocNode\\SymfonyRouteTagValueNodeFactory' => __DIR__ . '/..' . '/rector/rector-symfony/src/PhpDocNode/SymfonyRouteTagValueNodeFactory.php',
'Rector\\Symfony\\PhpParser\\NamedSimplePhpParser' => __DIR__ . '/..' . '/rector/rector-symfony/src/PhpParser/NamedSimplePhpParser.php',
'Rector\\Symfony\\Set\\FOSRestSetList' => __DIR__ . '/..' . '/rector/rector-symfony/src/Set/FOSRestSetList.php',
'Rector\\Symfony\\Set\\JMSSetList' => __DIR__ . '/..' . '/rector/rector-symfony/src/Set/JMSSetList.php',
'Rector\\Symfony\\Set\\SensiolabsSetList' => __DIR__ . '/..' . '/rector/rector-symfony/src/Set/SensiolabsSetList.php',

View File

@ -1867,12 +1867,12 @@
"source": {
"type": "git",
"url": "https:\/\/github.com\/rectorphp\/rector-symfony.git",
"reference": "777bf13ad323904a699cd86786cdf89e1bf5f49f"
"reference": "1fcf66e8c1473ba1776e0adbb64772e6e7af4f8c"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-symfony\/zipball\/777bf13ad323904a699cd86786cdf89e1bf5f49f",
"reference": "777bf13ad323904a699cd86786cdf89e1bf5f49f",
"url": "https:\/\/api.github.com\/repos\/rectorphp\/rector-symfony\/zipball\/1fcf66e8c1473ba1776e0adbb64772e6e7af4f8c",
"reference": "1fcf66e8c1473ba1776e0adbb64772e6e7af4f8c",
"shasum": ""
},
"require": {
@ -1894,14 +1894,14 @@
"symfony\/security-http": "^6.4",
"symfony\/validator": "^6.4",
"symplify\/easy-coding-standard": "^12.3",
"symplify\/phpstan-extensions": "^11.1",
"symplify\/phpstan-extensions": "^11.4",
"symplify\/phpstan-rules": "^13.0",
"symplify\/rule-doc-generator": "^12.2.2",
"symplify\/vendor-patches": "^11.2",
"symplify\/vendor-patches": "^11.3",
"tomasvotruba\/class-leak": "^0.2",
"tracy\/tracy": "^2.10"
},
"time": "2024-07-02T12:14:53+00:00",
"time": "2024-07-04T11:37:50+00:00",
"default-branch": true,
"type": "rector-extension",
"extra": {

File diff suppressed because one or more lines are too long

View File

@ -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 78fa974'), '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 c053b97'), '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 887a20a'), '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 777bf13'));
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 78fa974'), '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 c053b97'), '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 887a20a'), '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 1fcf66e'));
private function __construct()
{
}

View File

@ -22,10 +22,10 @@
"symfony\/security-http": "^6.4",
"symfony\/validator": "^6.4",
"symplify\/easy-coding-standard": "^12.3",
"symplify\/phpstan-extensions": "^11.1",
"symplify\/phpstan-extensions": "^11.4",
"symplify\/phpstan-rules": "^13.0",
"symplify\/rule-doc-generator": "^12.2.2",
"symplify\/vendor-patches": "^11.2",
"symplify\/vendor-patches": "^11.3",
"tomasvotruba\/class-leak": "^0.2",
"tracy\/tracy": "^2.10"
},

View File

@ -3,6 +3,7 @@
declare (strict_types=1);
namespace RectorPrefix202407;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StringType;
use PHPStan\Type\UnionType;
@ -16,5 +17,5 @@ use Rector\TypeDeclaration\ValueObject\AddReturnTypeDeclaration;
return static function (RectorConfig $rectorConfig) : void {
$rectorConfig->ruleWithConfiguration(RenameClassRector::class, ['Symfony\\Component\\HttpKernel\\UriSigner' => 'Symfony\\Component\\HttpFoundation\\UriSigner', 'Symfony\\Component\\HttpKernel\\Debug\\FileLinkFormatter' => 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\FileLinkFormatter']);
$rectorConfig->ruleWithConfiguration(RenameAttributeRector::class, [new RenameAttribute('Symfony\\Component\\Routing\\Annotation\\Route', 'Symfony\\Component\\Routing\\Attribute\\Route')]);
$rectorConfig->ruleWithConfiguration(AddReturnTypeDeclarationRector::class, [new AddReturnTypeDeclaration('Symfony\\Component\\Form\\DataTransformerInterface', 'transform', new UnionType([new StringType(), new \PHPStan\Type\NullType()])), new AddReturnTypeDeclaration('Symfony\\Component\\Form\\DataTransformerInterface', 'reverseTransform', new UnionType([new StringType(), new ObjectWithoutClassType()]))]);
$rectorConfig->ruleWithConfiguration(AddReturnTypeDeclarationRector::class, [new AddReturnTypeDeclaration('Symfony\\Component\\Form\\DataTransformerInterface', 'transform', new UnionType([new StringType(), new NullType()])), new AddReturnTypeDeclaration('Symfony\\Component\\Form\\DataTransformerInterface', 'reverseTransform', new UnionType([new StringType(), new ObjectWithoutClassType()]))]);
};

View File

@ -1,4 +1,4 @@
# 82 Rules Overview
# 83 Rules Overview
## ActionSuffixRemoverRector
@ -122,6 +122,31 @@ Change `$this->authorizationChecker->isGranted([$a, $b])` to `$this->authorizati
<br>
## AutowireAttributeRector
Change explicit configuration parameter pass into #[Autowire] attributes
:wrench: **configure it!**
- class: [`Rector\Symfony\Configs\Rector\Class_\AutowireAttributeRector`](../rules/Configs/Rector/Class_/AutowireAttributeRector.php)
```diff
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
+
final class SomeClass
{
public function __construct(
+ #[Autowire(param: 'timeout')]
private int $timeout,
+ #[Autowire(env: 'APP_SECRET')]
private string $secret,
) {
}
}
```
<br>
## BinaryFileResponseCreateToNewInstanceRector
Change deprecated `BinaryFileResponse::create()` to use `__construct()` instead

View File

@ -15,7 +15,7 @@ final class SecurityAccessDecisionManagerConfigArrayHandler
/**
* @return array<Expression<MethodCall>>
*/
public function handle(Array_ $array, Variable $configCaller, string $mainMethodName) : array
public function handle(Array_ $array, Variable $variable, string $mainMethodName) : array
{
if (!$array->items[0] instanceof ArrayItem) {
return [];
@ -26,7 +26,7 @@ final class SecurityAccessDecisionManagerConfigArrayHandler
return [];
}
// build accessControl() method call here
$accessDecisionManagerMethodCall = new MethodCall($configCaller, $mainMethodName);
$accessDecisionManagerMethodCall = new MethodCall($variable, $mainMethodName);
foreach ($nestedArray->items as $nestedArrayItem) {
if (!$nestedArrayItem instanceof ArrayItem) {
continue;

View File

@ -0,0 +1,48 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Configs\NodeAnalyser;
use PhpParser\NodeTraverser;
use Rector\Symfony\Configs\NodeVisitor\CollectServiceArgumentsNodeVisitor;
use Rector\Symfony\Configs\ValueObject\ServiceArguments;
use Rector\Symfony\PhpParser\NamedSimplePhpParser;
use RectorPrefix202407\Symfony\Component\Finder\SplFileInfo;
final class ConfigServiceArgumentsResolver
{
/**
* @readonly
* @var \Rector\Symfony\PhpParser\NamedSimplePhpParser
*/
private $namedSimplePhpParser;
/**
* @var \PhpParser\NodeTraverser
*/
private $nodeTraverser;
/**
* @var \Rector\Symfony\Configs\NodeVisitor\CollectServiceArgumentsNodeVisitor
*/
private $collectServiceArgumentsNodeVisitor;
public function __construct(NamedSimplePhpParser $namedSimplePhpParser)
{
$this->namedSimplePhpParser = $namedSimplePhpParser;
$this->nodeTraverser = new NodeTraverser();
$this->collectServiceArgumentsNodeVisitor = new CollectServiceArgumentsNodeVisitor();
$this->nodeTraverser->addVisitor($this->collectServiceArgumentsNodeVisitor);
}
/**
* @param SplFileInfo[] $phpConfigFileInfos
* @return ServiceArguments[]
*/
public function resolve(array $phpConfigFileInfos) : array
{
$servicesArguments = [];
foreach ($phpConfigFileInfos as $phpConfigFileInfo) {
// traverse and collect data
$configStmts = $this->namedSimplePhpParser->parseString($phpConfigFileInfo->getContents());
$this->nodeTraverser->traverse($configStmts);
$servicesArguments = \array_merge($servicesArguments, $this->collectServiceArgumentsNodeVisitor->getServicesArguments());
}
return $servicesArguments;
}
}

View File

@ -0,0 +1,83 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Configs\NodeAnalyser;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\NodeFinder;
final class SetServiceClassNameResolver
{
/**
* Looks for
*
* $services->set(SomeClassName::Class)
*
*
* "SomeClassName"
*/
public function resolve(MethodCall $methodCall) : ?string
{
$nodeFinder = new NodeFinder();
$serviceClassName = null;
$nodeFinder->findFirst($methodCall, function (Node $node) use(&$serviceClassName) : ?bool {
if (!$node instanceof MethodCall) {
return null;
}
if (!$node->name instanceof Identifier) {
return null;
}
// we look for services variable
if (!$this->isServicesVariable($node->var)) {
return null;
}
// dump($methodCall->var);
$args = $node->getArgs();
foreach ($args as $arg) {
if (!$arg->value instanceof ClassConstFetch) {
continue;
}
$resolvedClassConstantName = $this->matchClassConstantName($arg->value);
if (!\is_string($resolvedClassConstantName)) {
continue;
}
$serviceClassName = $resolvedClassConstantName;
return \true;
}
return \false;
});
return $serviceClassName;
}
public function matchClassConstantName(ClassConstFetch $classConstFetch) : ?string
{
if (!$classConstFetch->name instanceof Identifier) {
return null;
}
if ($classConstFetch->name->toString() !== 'class') {
return null;
}
if (!$classConstFetch->class instanceof FullyQualified) {
return null;
}
return $classConstFetch->class->toString();
}
/**
* Dummy name check, as we don't have types here, only variable names.
*/
private function isServicesVariable(Expr $expr) : bool
{
if (!$expr instanceof Variable) {
return \false;
}
if (!\is_string($expr->name)) {
return \false;
}
$servicesName = $expr->name;
return $servicesName === 'services';
}
}

View File

@ -0,0 +1,110 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Configs\NodeVisitor;
use RectorPrefix202407\Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\NodeVisitorAbstract;
use Rector\Exception\NotImplementedYetException;
use Rector\Symfony\Configs\NodeAnalyser\SetServiceClassNameResolver;
use Rector\Symfony\Configs\ValueObject\ServiceArguments;
final class CollectServiceArgumentsNodeVisitor extends NodeVisitorAbstract
{
/**
* @var string
*/
private const ENVS = 'envs';
/**
* @var string
*/
private const PARAMETERS = 'parameters';
/**
* @var array<string, array<self::ENVS|self::PARAMETERS, string[]>>
*/
private $servicesArgumentsByClass = [];
/**
* @readonly
* @var \Rector\Symfony\Configs\NodeAnalyser\SetServiceClassNameResolver
*/
private $setServiceClassNameResolver;
public function __construct()
{
$this->setServiceClassNameResolver = new SetServiceClassNameResolver();
}
/**
* @param Stmt[] $nodes
*/
public function beforeTraverse(array $nodes)
{
$this->servicesArgumentsByClass = [];
return parent::beforeTraverse($nodes);
}
public function enterNode(Node $node) : ?Node
{
$argMethodCall = $this->matchArgMethodCall($node);
if (!$argMethodCall instanceof MethodCall) {
return null;
}
// 1. detect arg name + value
$firstArg = $argMethodCall->getArgs()[0];
if ($firstArg->value instanceof String_ || $firstArg->value instanceof Node\Scalar\LNumber) {
$argumentLocator = $firstArg->value->value;
} else {
throw new NotImplementedYetException(\sprintf('Add support for non-string arg names like "%s"', \get_class($firstArg->value)));
}
$secondArg = $argMethodCall->getArgs()[1];
if (!$secondArg->value instanceof String_) {
throw new NotImplementedYetException(\sprintf('Add support for non-string arg values like "%s"', \get_class($firstArg->value)));
}
$serviceClassName = $this->setServiceClassNameResolver->resolve($argMethodCall);
if (!\is_string($serviceClassName)) {
return null;
}
$argumentValue = $secondArg->value->value;
$match = Strings::match($argumentValue, '#%env\\((?<env>[A-Z_]+)\\)#');
if (isset($match['env'])) {
$this->servicesArgumentsByClass[$serviceClassName][self::ENVS][$argumentLocator] = (string) $match['env'];
return null;
}
$match = Strings::match($argumentValue, '#%(?<parameter>[\\w]+)%#');
if (isset($match['parameter'])) {
$this->servicesArgumentsByClass[$serviceClassName][self::PARAMETERS][$argumentLocator] = (string) $match['parameter'];
return null;
}
return null;
}
/**
* @return ServiceArguments[]
*/
public function getServicesArguments() : array
{
$serviceArguments = [];
foreach ($this->servicesArgumentsByClass as $serviceClass => $arguments) {
$parameters = $arguments[self::PARAMETERS] ?? [];
$envs = $arguments[self::ENVS] ?? [];
$serviceArguments[] = new ServiceArguments($serviceClass, $parameters, $envs);
}
return $serviceArguments;
}
/**
* We look for: ->arg(..., ...)
*/
private function matchArgMethodCall(Node $node) : ?MethodCall
{
if (!$node instanceof MethodCall) {
return null;
}
if (!$node->name instanceof Identifier) {
return null;
}
if ($node->name->toString() !== 'arg') {
return null;
}
return $node;
}
}

View File

@ -0,0 +1,168 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Configs\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Exception\ShouldNotHappenException;
use Rector\Rector\AbstractRector;
use Rector\Symfony\Configs\NodeAnalyser\ConfigServiceArgumentsResolver;
use Rector\ValueObject\MethodName;
use RectorPrefix202407\Symfony\Component\Finder\Finder;
use RectorPrefix202407\Symfony\Component\Finder\SplFileInfo;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use RectorPrefix202407\Webmozart\Assert\Assert;
/**
* @see https://symfony.com/blog/new-in-symfony-6-1-service-autowiring-attributes
*/
final class AutowireAttributeRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @readonly
* @var \Rector\Symfony\Configs\NodeAnalyser\ConfigServiceArgumentsResolver
*/
private $configServiceArgumentsResolver;
/**
* @var string
*/
public const CONFIGS_DIRECTORY = 'configs_directory';
/**
* @var string
*/
private const AUTOWIRE_CLASS = 'Symfony\\Component\\DependencyInjection\\Attribute\\Autowire';
/**
* @var string|null
*/
private $configsDirectory;
public function __construct(ConfigServiceArgumentsResolver $configServiceArgumentsResolver)
{
$this->configServiceArgumentsResolver = $configServiceArgumentsResolver;
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Change explicit configuration parameter pass into #[Autowire] attributes', [new ConfiguredCodeSample(<<<'CODE_SAMPLE'
final class SomeClass
{
public function __construct(
private int $timeout,
private string $secret,
) {
}
}
CODE_SAMPLE
, <<<'CODE_SAMPLE'
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final class SomeClass
{
public function __construct(
#[Autowire(param: 'timeout')]
private int $timeout,
#[Autowire(env: 'APP_SECRET')]
private string $secret,
) {
}
}
CODE_SAMPLE
, [self::CONFIGS_DIRECTORY => __DIR__ . '/config'])]);
}
public function getNodeTypes() : array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node) : ?Class_
{
if ($node->isAnonymous()) {
return null;
}
$constructClassMethod = $node->getMethod(MethodName::CONSTRUCT);
if (!$constructClassMethod instanceof ClassMethod) {
return null;
}
if ($this->configsDirectory === null) {
throw new ShouldNotHappenException('Configure paths first');
}
$phpConfigFileInfos = $this->findPhpConfigs($this->configsDirectory);
$servicesArguments = $this->configServiceArgumentsResolver->resolve($phpConfigFileInfos);
if ($servicesArguments === []) {
// nothing to resolve, maybe false positive!
return null;
}
$className = $this->getName($node);
if (!\is_string($className)) {
return null;
}
$hasChanged = \false;
foreach ($servicesArguments as $serviceArgument) {
if ($className !== $serviceArgument->getClassName()) {
continue;
}
foreach ($constructClassMethod->params as $position => $constructorParam) {
if (!$constructorParam->var instanceof Variable) {
continue;
}
$constructorParameterName = $constructorParam->var->name;
if (!\is_string($constructorParameterName)) {
continue;
}
$currentEnv = $serviceArgument->getEnvs()[$constructorParameterName] ?? $serviceArgument->getEnvs()[$position] ?? null;
if ($currentEnv) {
$constructorParam->attrGroups[] = new AttributeGroup([$this->createAutowireAttribute($currentEnv, 'env')]);
$hasChanged = \true;
}
$currentParameter = $serviceArgument->getParams()[$constructorParameterName] ?? $serviceArgument->getParams()[$position] ?? null;
if ($currentParameter) {
$constructorParam->attrGroups[] = new AttributeGroup([$this->createAutowireAttribute($currentParameter, 'param')]);
$hasChanged = \true;
}
}
}
if ($hasChanged) {
return $node;
}
return null;
}
/**
* @param mixed[] $configuration
*/
public function configure(array $configuration) : void
{
if (!$configuration[self::CONFIGS_DIRECTORY]) {
return;
}
$configsDirectory = $configuration[self::CONFIGS_DIRECTORY];
Assert::string($configsDirectory);
Assert::directory($configsDirectory);
$this->configsDirectory = $configsDirectory;
}
/**
* @return SplFileInfo[]
*/
private function findPhpConfigs(string $configsDirectory) : array
{
$phpConfigsFinder = Finder::create()->files()->in($configsDirectory)->name('*.php')->sortByName();
if ($phpConfigsFinder->count() === 0) {
throw new ShouldNotHappenException(\sprintf('Could not find any PHP configs in "%s"', $this->configsDirectory));
}
return \iterator_to_array($phpConfigsFinder->getIterator());
}
private function createAutowireAttribute(string $value, string $argName) : Attribute
{
$args = [new Arg(new String_($value), \false, \false, [], new Identifier($argName))];
return new Attribute(new FullyQualified(self::AUTOWIRE_CLASS), $args);
}
}

View File

@ -0,0 +1,51 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\Configs\ValueObject;
final class ServiceArguments
{
/**
* @readonly
* @var string
*/
private $className;
/**
* @var array<(string | int), string>
* @readonly
*/
private $params;
/**
* @var array<(string | int), string>
* @readonly
*/
private $envs;
/**
* @param array<string|int, string> $params
* @param array<string|int, string> $envs
*/
public function __construct(string $className, array $params, array $envs)
{
$this->className = $className;
$this->params = $params;
$this->envs = $envs;
}
public function getClassName() : string
{
return $this->className;
}
/**
* @return array<string|int, string>
*/
public function getParams() : array
{
return $this->params;
}
/**
* @return array<string|int, string>
*/
public function getEnvs() : array
{
return $this->envs;
}
}

View File

@ -6,7 +6,6 @@ namespace Rector\Symfony\Symfony40\Rector\MethodCall;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use Rector\Configuration\Deprecation\Contract\DeprecatedInterface;
use Rector\Contract\DependencyInjection\ResetableInterface;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

View File

@ -15,7 +15,6 @@ use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\ObjectType;
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
use Rector\Rector\AbstractRector;
use Rector\Symfony\Enum\SymfonyAnnotation;

View File

@ -0,0 +1,36 @@
<?php
declare (strict_types=1);
namespace Rector\Symfony\PhpParser;
use PhpParser\Node\Stmt;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\Parser;
use PhpParser\ParserFactory;
final class NamedSimplePhpParser
{
/**
* @var \PhpParser\Parser
*/
private $phpParser;
public function __construct()
{
$parserFactory = new ParserFactory();
$this->phpParser = $parserFactory->create(ParserFactory::ONLY_PHP7);
}
/**
* @return Stmt[]
*/
public function parseString(string $content) : array
{
$stmts = $this->phpParser->parse($content);
if ($stmts === null) {
return [];
}
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor(new NameResolver());
$nodeTraverser->traverse($stmts);
return $stmts;
}
}