Add meta node FileWithoutNamespace (#4355)

* [CakePHP] Promote AppUsesStaticCallToUseStatementRector to File

* [CakePHP] Change ImplicitShortClassNameUseStatementRector to FileWithoutNamespace approach

* [Renaming] Update PseudoNamespaceToNamespaceRector to use FileWithnoutNamespace

* [rector] [Renaming] Update PseudoNamespaceToNamespaceRector to use FileWithnoutNamespace

* [cs] [Renaming] Update PseudoNamespaceToNamespaceRector to use FileWithnoutNamespace

Co-authored-by: rector-bot <tomas@getrector.org>
This commit is contained in:
Tomas Votruba 2020-10-05 10:39:02 +02:00 committed by GitHub
parent 7e124dffc0
commit 1c8fac5242
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 821 additions and 393 deletions

View File

@ -245,14 +245,14 @@
"Rector\\PHPStanStaticTypeMapper\\Tests\\": "packages/phpstan-static-type-mapper/tests"
},
"classmap": [
"rules/cakephp/tests/Rector/Name/ImplicitShortClassNameUseStatementRector/Source",
"rules/cakephp/tests/Rector/FileWithoutNamespace/ImplicitShortClassNameUseStatementRector/Source",
"rules/symfony/tests/Rector/MethodCall/ContainerGetToConstructorInjectionRector/Source",
"rules/autodiscovery/tests/Rector/FileSystem/MoveInterfacesToContractNamespaceDirectoryRector/Expected",
"rules/autodiscovery/tests/Rector/FileSystem/MoveServicesBySuffixToDirectoryRector/Expected",
"rules/cakephp/tests/Rector/Expression/AppUsesStaticCallToUseStatementRector/Source",
"rules/cakephp/tests/Rector/Namespace_/AppUsesStaticCallToUseStatementRector/Source",
"tests/Source",
"rules/psr4/tests/Rector/MultipleClassFileToPsr4ClassesRector/Source",
"rules/generic/tests/Rector/Name/PseudoNamespaceToNamespaceRector/Source"
"rules/renaming/tests/Rector/FileWithoutNamespace/PseudoNamespaceToNamespaceRector/Source"
],
"files": [
"rules/naming/tests/ValueObjectFactory/PropertyRenameFactory/Fixture/SomeClass.php.inc",
@ -266,7 +266,7 @@
"rules/renaming/tests/Rector/Name/RenameClassRector/Source/Twig_Extension_Sandbox.php",
"rules/renaming/tests/Rector/Name/RenameClassRector/Source/TwigFilter.php",
"rules/renaming/tests/Rector/Name/RenameClassRector/Source/Manual_Twig_Filter.php",
"rules/generic/tests/Rector/Name/PseudoNamespaceToNamespaceRector/Source/ChangeMeAnotherNamespace.php",
"rules/renaming/tests/Rector/FileWithoutNamespace/PseudoNamespaceToNamespaceRector/Source/ChangeMeAnotherNamespace.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/Foo.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/Function_/count.php",
"rules/coding-style/tests/Rector/Namespace_/ImportFullyQualifiedNamesRector/Source/AnotherClass.php",

View File

@ -53,6 +53,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
__DIR__ . '/../src/ValueObject',
__DIR__ . '/../src/Configuration/MinimalVersionChecker',
__DIR__ . '/../src/Bootstrap',
__DIR__ . '/../src/PhpParser/Node/CustomNode',
// loaded for PHPStan factory
__DIR__ . '/../src/PHPStan/Type',
]);

View File

@ -2,8 +2,8 @@
declare(strict_types=1);
use Rector\CakePHP\Rector\Expression\AppUsesStaticCallToUseStatementRector;
use Rector\CakePHP\Rector\Name\ImplicitShortClassNameUseStatementRector;
use Rector\CakePHP\Rector\FileWithoutNamespace\ImplicitShortClassNameUseStatementRector;
use Rector\CakePHP\Rector\Namespace_\AppUsesStaticCallToUseStatementRector;
use Rector\Renaming\Rector\Name\RenameClassRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

View File

@ -2,11 +2,10 @@
declare(strict_types=1);
use Rector\Generic\Rector\Name\PseudoNamespaceToNamespaceRector;
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\PHPUnit\Rector\ClassMethod\AddDoesNotPerformAssertionToNonAssertingTestRector;
use Rector\PHPUnit\Rector\MethodCall\GetMockBuilderGetMockToCreateMockRector;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Rector\Renaming\Rector\MethodCall\RenameMethodRector;
use Rector\Renaming\Rector\Name\RenameClassRector;
use Rector\Renaming\ValueObject\MethodCallRename;

View File

@ -2,8 +2,8 @@
declare(strict_types=1);
use Rector\Generic\Rector\Name\PseudoNamespaceToNamespaceRector;
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Rector\Renaming\Rector\Name\RenameClassRector;
use function Rector\SymfonyPhpConfig\inline_value_objects;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

View File

@ -1,4 +1,4 @@
# All 583 Rectors Overview
# All 584 Rectors Overview
- [Projects](#projects)
---
@ -9,7 +9,7 @@
- [Autodiscovery](#autodiscovery) (4)
- [CakePHP](#cakephp) (6)
- [CodeQuality](#codequality) (59)
- [CodingStyle](#codingstyle) (34)
- [CodingStyle](#codingstyle) (33)
- [DeadCode](#deadcode) (40)
- [Decouple](#decouple) (1)
- [Doctrine](#doctrine) (17)
@ -29,7 +29,7 @@
- [MockeryToProphecy](#mockerytoprophecy) (2)
- [MockistaToMockery](#mockistatomockery) (2)
- [MysqlToMysqli](#mysqltomysqli) (4)
- [Naming](#naming) (10)
- [Naming](#naming) (11)
- [Nette](#nette) (16)
- [NetteCodeQuality](#nettecodequality) (6)
- [NetteKdyby](#nettekdyby) (4)
@ -66,7 +66,7 @@
- [SOLID](#solid) (12)
- [Sensio](#sensio) (3)
- [StrictCodeQuality](#strictcodequality) (1)
- [Symfony](#symfony) (33)
- [Symfony](#symfony) (34)
- [SymfonyCodeQuality](#symfonycodequality) (1)
- [SymfonyPHPUnit](#symfonyphpunit) (1)
- [SymfonyPhpConfig](#symfonyphpconfig) (2)
@ -269,8 +269,8 @@ return static function (ContainerConfigurator $containerConfigurator): void {
### `AppUsesStaticCallToUseStatementRector`
- class: [`Rector\CakePHP\Rector\Expression\AppUsesStaticCallToUseStatementRector`](/rules/cakephp/src/Rector/Expression/AppUsesStaticCallToUseStatementRector.php)
- [test fixtures](/rules/cakephp/tests/Rector/Expression/AppUsesStaticCallToUseStatementRector/Fixture)
- class: [`Rector\CakePHP\Rector\Namespace_\AppUsesStaticCallToUseStatementRector`](/rules/cakephp/src/Rector/Namespace_/AppUsesStaticCallToUseStatementRector.php)
- [test fixtures](/rules/cakephp/tests/Rector/Namespace_/AppUsesStaticCallToUseStatementRector/Fixture)
Change `App::uses()` to use imports
@ -363,8 +363,8 @@ Changes `$fixtues` style from snake_case to PascalCase.
### `ImplicitShortClassNameUseStatementRector`
- class: [`Rector\CakePHP\Rector\Name\ImplicitShortClassNameUseStatementRector`](/rules/cakephp/src/Rector/Name/ImplicitShortClassNameUseStatementRector.php)
- [test fixtures](/rules/cakephp/tests/Rector/Name/ImplicitShortClassNameUseStatementRector/Fixture)
- class: [`Rector\CakePHP\Rector\FileWithoutNamespace\ImplicitShortClassNameUseStatementRector`](/rules/cakephp/src/Rector/FileWithoutNamespace/ImplicitShortClassNameUseStatementRector.php)
- [test fixtures](/rules/cakephp/tests/Rector/FileWithoutNamespace/ImplicitShortClassNameUseStatementRector/Fixture)
Collect implicit class names and add imports
@ -2403,26 +2403,6 @@ Assign outcome of ternary condition to variable, where applicable
<br><br>
### `UnderscoreToCamelCaseLocalVariableNameRector`
- class: [`Rector\CodingStyle\Rector\Variable\UnderscoreToCamelCaseLocalVariableNameRector`](/rules/coding-style/src/Rector/Variable/UnderscoreToCamelCaseLocalVariableNameRector.php)
- [test fixtures](/rules/coding-style/tests/Rector/Variable/UnderscoreToCamelCaseLocalVariableNameRector/Fixture)
Change under_score local variable names to camelCase
```diff
final class SomeClass
{
public function run($a_b)
{
- $some_value = $a_b;
+ $someValue = $a_b;
}
}
```
<br><br>
### `UseClassKeywordForClassNameResolutionRector`
- class: [`Rector\CodingStyle\Rector\String_\UseClassKeywordForClassNameResolutionRector`](/rules/coding-style/src/Rector/String_/UseClassKeywordForClassNameResolutionRector.php)
@ -6361,7 +6341,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
### `PseudoNamespaceToNamespaceRector`
- class: [`Rector\Generic\Rector\Name\PseudoNamespaceToNamespaceRector`](/rules/generic/src/Rector/Name/PseudoNamespaceToNamespaceRector.php)
- class: [`Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector`](/rules/generic/src/Rector/Name/PseudoNamespaceToNamespaceRector.php)
- [test fixtures](/rules/generic/tests/Rector/Name/PseudoNamespaceToNamespaceRector/Fixture)
Replaces defined Pseudo_Namespaces by Namespace\Ones.
@ -6371,8 +6351,8 @@ Replaces defined Pseudo_Namespaces by Namespace\Ones.
declare(strict_types=1);
use Rector\Generic\Rector\Name\PseudoNamespaceToNamespaceRector;
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use function Rector\SymfonyPhpConfig\inline_value_objects;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
@ -7805,6 +7785,26 @@ Rename variable to match new ClassType
<br><br>
### `UnderscoreToCamelCaseLocalVariableNameRector`
- class: [`Rector\Naming\Rector\Variable\UnderscoreToCamelCaseLocalVariableNameRector`](/rules/naming/src/Rector/Variable/UnderscoreToCamelCaseLocalVariableNameRector.php)
- [test fixtures](/rules/naming/tests/Rector/Variable/UnderscoreToCamelCaseLocalVariableNameRector/Fixture)
Change under_score local variable names to camelCase
```diff
final class SomeClass
{
public function run($a_b)
{
- $some_value = $a_b;
+ $someValue = $a_b;
}
}
```
<br><br>
### `UnderscoreToCamelCasePropertyNameRector`
- class: [`Rector\Naming\Rector\Property\UnderscoreToCamelCasePropertyNameRector`](/rules/naming/src/Rector/Property/UnderscoreToCamelCasePropertyNameRector.php)
@ -14428,6 +14428,26 @@ Turns long flash adding to short helper method in Controller in Symfony
<br><br>
### `AutoWireWithClassNameSuffixForMethodWithRequiredAnnotationRector`
- class: [`Rector\Symfony\Rector\ClassMethod\AutoWireWithClassNameSuffixForMethodWithRequiredAnnotationRector`](/rules/symfony/src/Rector/ClassMethod/AutoWireWithClassNameSuffixForMethodWithRequiredAnnotationRector.php)
- [test fixtures](/rules/symfony/tests/Rector/ClassMethod/AutoWireWithClassNameSuffixForMethodWithRequiredAnnotationRector/Fixture)
Use autowire + class name suffix for method with @required annotation
```diff
class SomeClass
{
/** @required */
- public function foo()
+ public function autowireSomeClass()
{
}
}
```
<br><br>
### `CascadeValidationFormBuilderRector`
- class: [`Rector\Symfony\Rector\MethodCall\CascadeValidationFormBuilderRector`](/rules/symfony/src/Rector/MethodCall/CascadeValidationFormBuilderRector.php)

View File

@ -87,6 +87,15 @@ services:
'Hoa\Protocol\Node\Node': 'PhpParser\Node'
'Nette\Utils\FileSystem': 'Symplify\SmartFileSystem\SmartFileSystem'
'Symfony\Component\Filesystem\Filesystem': 'Symplify\SmartFileSystem\SmartFileSystem'
# builder typo nodes
PhpParser\Builder\Use_: Rector\Core\PhpParser\Builder\UseBuilder
PhpParser\Builder\Class_: Rector\Core\PhpParser\Builder\ClassBuilder
PhpParser\Builder\Method: Rector\Core\PhpParser\Builder\MethodBuilder
PhpParser\Builder\Namespace_: Rector\Core\PhpParser\Builder\NamespaceBuilder
PhpParser\Builder\Param: Rector\Core\PhpParser\Builder\ParamBuilder
PhpParser\Builder\Property: Rector\Core\PhpParser\Builder\PropertyBuilder
PhpParser\Builder\TraitUse: Rector\Core\PhpParser\Builder\TraitUseBuilder
parameters:
level: max

View File

@ -44,7 +44,9 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$parameters->set(Option::AUTOLOAD_PATHS, [__DIR__ . '/compiler/src']);
$parameters->set(Option::EXCLUDE_PATHS, [
'/Fixture/', '/Source/', '/Expected/',
'/Fixture/',
'/Source/',
'/Expected/',
__DIR__ . '/packages/doctrine-annotation-generated/src/*',
// tempalte files
__DIR__ . '/packages/rector-generator/templates/*',

View File

@ -1,120 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\CakePHP\Rector\Expression;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use Rector\CakePHP\Naming\CakePHPFullyQualifiedClassNameResolver;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
/**
* @see https://github.com/cakephp/upgrade/blob/756410c8b7d5aff9daec3fa1fe750a3858d422ac/src/Shell/Task/AppUsesTask.php
* @see https://github.com/cakephp/upgrade/search?q=uses&unscoped_q=uses
*
* @see \Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\AppUsesStaticCallToUseStatementRectorTest
*/
final class AppUsesStaticCallToUseStatementRector extends AbstractRector
{
/**
* @var CakePHPFullyQualifiedClassNameResolver
*/
private $cakePHPFullyQualifiedClassNameResolver;
public function __construct(CakePHPFullyQualifiedClassNameResolver $cakePHPFullyQualifiedClassNameResolver)
{
$this->cakePHPFullyQualifiedClassNameResolver = $cakePHPFullyQualifiedClassNameResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change App::uses() to use imports', [
new CodeSample(
<<<'CODE_SAMPLE'
App::uses('NotificationListener', 'Event');
CakeEventManager::instance()->attach(new NotificationListener());
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Event\NotificationListener;
CakeEventManager::instance()->attach(new NotificationListener());
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Expression::class];
}
/**
* @param Expression $node
*/
public function refactor(Node $node): ?Node
{
if (! $node->expr instanceof StaticCall) {
return null;
}
$staticCall = $node->expr;
if (! $this->isAppUses($staticCall)) {
return null;
}
$fullyQualifiedName = $this->createFullyQualifiedNameFromAppUsesStaticCall($staticCall);
// A. is above the class or under the namespace
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Namespace_ || $parentNode === null) {
return $this->createUseFromFullyQualifiedName($fullyQualifiedName);
}
// B. is inside the code → add use import
$this->addUseType(new FullyQualifiedObjectType($fullyQualifiedName), $node);
$this->removeNode($node);
return null;
}
private function isAppUses(StaticCall $staticCall): bool
{
return $this->isStaticCallNamed($staticCall, 'App', 'uses');
}
private function createFullyQualifiedNameFromAppUsesStaticCall(StaticCall $staticCall): string
{
/** @var string $shortClassName */
$shortClassName = $this->getValue($staticCall->args[0]->value);
/** @var string $namespaceName */
$namespaceName = $this->getValue($staticCall->args[1]->value);
return $this->cakePHPFullyQualifiedClassNameResolver->resolveFromPseudoNamespaceAndShortClassName(
$namespaceName,
$shortClassName
);
}
private function createUseFromFullyQualifiedName(string $fullyQualifiedName): Use_
{
$useUse = new UseUse(new Name($fullyQualifiedName));
return new Use_([$useUse]);
}
}

View File

@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace Rector\CakePHP\Rector\FileWithoutNamespace;
use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name;
use Rector\CakePHP\ImplicitNameResolver;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see https://github.com/cakephp/upgrade/blob/05d85c147bb1302b576b818cabb66a40462aaed0/src/Shell/Task/AppUsesTask.php#L183
*
* @see \Rector\CakePHP\Tests\Rector\FileWithoutNamespace\ImplicitShortClassNameUseStatementRector\ImplicitShortClassNameUseStatementRectorTest
*/
final class ImplicitShortClassNameUseStatementRector extends AbstractRector
{
/**
* @var ImplicitNameResolver
*/
private $implicitNameResolver;
public function __construct(ImplicitNameResolver $implicitNameResolver)
{
$this->implicitNameResolver = $implicitNameResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Collect implicit class names and add imports', [
new CodeSample(
<<<'CODE_SAMPLE'
use App\Foo\Plugin;
class LocationsFixture extends TestFixture implements Plugin
{
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use App\Foo\Plugin;
use Cake\TestSuite\Fixture\TestFixture;
class LocationsFixture extends TestFixture implements Plugin
{
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [FileWithoutNamespace::class];
}
/**
* @param FileWithoutNamespace $node
*/
public function refactor(Node $node): ?Node
{
$names = $this->findNames($node);
/** @var Name[] $names */
if ($names === []) {
return null;
}
$resolvedNames = $this->resolveNames($names);
if ($resolvedNames === []) {
return null;
}
$uses = $this->nodeFactory->createUsesFromNames($resolvedNames);
$node->stmts = array_merge($uses, $node->stmts);
return $node;
}
/**
* @return Name[]
*/
private function findNames(FileWithoutNamespace $fileWithoutNamespace): array
{
return $this->betterNodeFinder->find($fileWithoutNamespace->stmts, function (Node $node): bool {
if (! $node instanceof Name) {
return false;
}
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
return ! $parent instanceof New_;
});
}
/**
* @param Name[] $names
* @return string[]
*/
private function resolveNames(array $names): array
{
$resolvedNames = [];
foreach ($names as $name) {
$classShortName = $this->getName($name);
$resolvedName = $this->implicitNameResolver->resolve($classShortName);
if ($resolvedName === null) {
continue;
}
$resolvedNames[] = $resolvedName;
}
return $resolvedNames;
}
}

View File

@ -1,87 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\CakePHP\Rector\Name;
use PhpParser\Node;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassLike;
use Rector\CakePHP\ImplicitNameResolver;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
/**
* @see https://github.com/cakephp/upgrade/blob/05d85c147bb1302b576b818cabb66a40462aaed0/src/Shell/Task/AppUsesTask.php#L183
*
* @see \Rector\CakePHP\Tests\Rector\Name\ImplicitShortClassNameUseStatementRector\ImplicitShortClassNameUseStatementRectorTest
*/
final class ImplicitShortClassNameUseStatementRector extends AbstractRector
{
/**
* @var ImplicitNameResolver
*/
private $implicitNameResolver;
public function __construct(ImplicitNameResolver $implicitNameResolver)
{
$this->implicitNameResolver = $implicitNameResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Collect implicit class names and add imports', [
new CodeSample(
<<<'CODE_SAMPLE'
use App\Foo\Plugin;
class LocationsFixture extends TestFixture implements Plugin
{
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use App\Foo\Plugin;
use Cake\TestSuite\Fixture\TestFixture;
class LocationsFixture extends TestFixture implements Plugin
{
}
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Name::class];
}
/**
* @param Name $node
*/
public function refactor(Node $node): ?Node
{
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if (! $parentNode instanceof ClassLike && $parentNode instanceof New_) {
return null;
}
$classShortName = $this->getName($node);
$resvoledName = $this->implicitNameResolver->resolve($classShortName);
if ($resvoledName === null) {
return null;
}
$this->addUseType(new FullyQualifiedObjectType($resvoledName), $node);
return null;
}
}

View File

@ -0,0 +1,173 @@
<?php
declare(strict_types=1);
namespace Rector\CakePHP\Rector\Namespace_;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\Declare_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Use_;
use Rector\CakePHP\Naming\CakePHPFullyQualifiedClassNameResolver;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
/**
* @see https://github.com/cakephp/upgrade/blob/756410c8b7d5aff9daec3fa1fe750a3858d422ac/src/Shell/Task/AppUsesTask.php
* @see https://github.com/cakephp/upgrade/search?q=uses&unscoped_q=uses
*
* @see \Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\AppUsesStaticCallToUseStatementRectorTest
*/
final class AppUsesStaticCallToUseStatementRector extends AbstractRector
{
/**
* @var CakePHPFullyQualifiedClassNameResolver
*/
private $cakePHPFullyQualifiedClassNameResolver;
public function __construct(CakePHPFullyQualifiedClassNameResolver $cakePHPFullyQualifiedClassNameResolver)
{
$this->cakePHPFullyQualifiedClassNameResolver = $cakePHPFullyQualifiedClassNameResolver;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change App::uses() to use imports', [
new CodeSample(
<<<'CODE_SAMPLE'
App::uses('NotificationListener', 'Event');
CakeEventManager::instance()->attach(new NotificationListener());
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Event\NotificationListener;
CakeEventManager::instance()->attach(new NotificationListener());
CODE_SAMPLE
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [FileWithoutNamespace::class, Namespace_::class];
}
/**
* @param FileWithoutNamespace|Namespace_ $node
*/
public function refactor(Node $node): ?Node
{
$appUsesStaticCalls = $this->collectAppUseStaticCalls($node);
if ($appUsesStaticCalls === []) {
return null;
}
$this->removeNodes($appUsesStaticCalls);
$names = $this->resolveNamesFromStaticCalls($appUsesStaticCalls);
$uses = $this->nodeFactory->createUsesFromNames($names);
if ($node instanceof Namespace_) {
$node->stmts = array_merge($uses, (array) $node->stmts);
return $node;
}
return $this->refactorFile($node, $uses);
}
/**
* @return StaticCall[]
*/
private function collectAppUseStaticCalls(Node $node): array
{
/** @var StaticCall[] $appUsesStaticCalls */
$appUsesStaticCalls = $this->betterNodeFinder->find($node, function (Node $node): bool {
if (! $node instanceof StaticCall) {
return false;
}
return $this->isStaticCallNamed($node, 'App', 'uses');
});
return $appUsesStaticCalls;
}
/**
* @param StaticCall[] $staticCalls
* @return string[]
*/
private function resolveNamesFromStaticCalls(array $staticCalls): array
{
$names = [];
foreach ($staticCalls as $staticCall) {
$names[] = $this->createFullyQualifiedNameFromAppUsesStaticCall($staticCall);
}
return $names;
}
/**
* @param Use_[] $uses
*/
private function refactorFile(FileWithoutNamespace $fileWithoutNamespace, array $uses): ?FileWithoutNamespace
{
$hasNamespace = $this->betterNodeFinder->findFirstInstanceOf($fileWithoutNamespace, Namespace_::class);
// already handled above
if ($hasNamespace !== null) {
return null;
}
$hasDeclare = $this->betterNodeFinder->findFirstInstanceOf($fileWithoutNamespace, Declare_::class);
if ($hasDeclare !== null) {
return $this->refactorFileWithDeclare($fileWithoutNamespace, $uses);
}
$fileWithoutNamespace->stmts = array_merge($uses, (array) $fileWithoutNamespace->stmts);
return $fileWithoutNamespace;
}
private function createFullyQualifiedNameFromAppUsesStaticCall(StaticCall $staticCall): string
{
/** @var string $shortClassName */
$shortClassName = $this->getValue($staticCall->args[0]->value);
/** @var string $namespaceName */
$namespaceName = $this->getValue($staticCall->args[1]->value);
return $this->cakePHPFullyQualifiedClassNameResolver->resolveFromPseudoNamespaceAndShortClassName(
$namespaceName,
$shortClassName
);
}
/**
* @param Use_[] $uses
*/
private function refactorFileWithDeclare(
FileWithoutNamespace $fileWithoutNamespace,
array $uses
): FileWithoutNamespace {
$newStmts = [];
foreach ($fileWithoutNamespace->stmts as $stmt) {
$newStmts[] = $stmt;
if ($stmt instanceof Declare_) {
foreach ($uses as $use) {
$newStmts[] = $use;
}
continue;
}
}
return new FileWithoutNamespace($newStmts);
}
}

View File

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Rector\CakePHP\Tests\Rector\Name\ImplicitShortClassNameUseStatementRector;
namespace Rector\CakePHP\Tests\Rector\FileWithoutNamespace\ImplicitShortClassNameUseStatementRector;
use Iterator;
use Rector\CakePHP\Rector\Name\ImplicitShortClassNameUseStatementRector;
use Rector\CakePHP\Rector\FileWithoutNamespace\ImplicitShortClassNameUseStatementRector;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

View File

@ -2,10 +2,10 @@
declare(strict_types=1);
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector;
use Iterator;
use Rector\CakePHP\Rector\Expression\AppUsesStaticCallToUseStatementRector;
use Rector\CakePHP\Rector\Namespace_\AppUsesStaticCallToUseStatementRector;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
\App::uses('Component', 'Controller');
@ -12,7 +12,7 @@ class CakeController
-----
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
use Cake\Controller\Component;

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
\App::uses('Component', 'Controller');
class CakeControllerWithStrictTypes
{
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
use Cake\Controller\Component;
class CakeControllerWithStrictTypes
{
}
?>

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
\App::uses('HttpSocket', 'Network/Http');
\App::uses('Xml', 'Utility');
@ -18,7 +18,7 @@ class CakePhpFixture
-----
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
use App\Network\Http\HttpSocket;
use Cake\Utility\Xml;

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
class ImportNamespacesUp
{
@ -17,7 +17,7 @@ class ImportNamespacesUp
-----
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
use Foo\Lib\HtmlDomLib;
use Foo\Lib\HtmlDomLibExt;

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
\App::uses('NotificationListener', 'Event');
@ -16,7 +16,7 @@ class SomeClass
-----
<?php
namespace Rector\CakePHP\Tests\Rector\Expression\AppUsesStaticCallToUseStatementRector\Fixture;
namespace Rector\CakePHP\Tests\Rector\Namespace_\AppUsesStaticCallToUseStatementRector\Fixture;
use Event\NotificationListener;

View File

@ -0,0 +1,27 @@
<?php
\App::uses('NotificationListener', 'Event');
class WithoutNamespace
{
public function run()
{
$values = new NotificationListener();
}
}
?>
-----
<?php
use Event\NotificationListener;
class WithoutNamespace
{
public function run()
{
$values = new NotificationListener();
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
\App::uses('NotificationListener', 'Event');
class WithoutNamespaceWithStrictTypes
{
public function run()
{
$values = new NotificationListener();
}
}
?>
-----
<?php
declare(strict_types=1);
use Event\NotificationListener;
class WithoutNamespaceWithStrictTypes
{
public function run()
{
$values = new NotificationListener();
}
}
?>

View File

@ -1,18 +1,26 @@
<?php
$newValue = null;
$values = [];
$input = '123';
namespace Rector\CodeQuality\Tests\Rector\Foreach_\SimplifyForeachToCoalescingRector\Fixture;
foreach ($values as $key => $value) {
if ($key === $input) {
$newValue = $value;
}
}
class SimpleIdenticalForeach
{
public function run()
{
$newValue = null;
$values = [];
$input = '123';
foreach ($values as $key => $value) {
if ($input === $key) {
$newValue = $value;
foreach ($values as $key => $value) {
if ($key === $input) {
$newValue = $value;
}
}
foreach ($values as $key => $value) {
if ($input === $key) {
$newValue = $value;
}
}
}
}
@ -20,12 +28,20 @@ foreach ($values as $key => $value) {
-----
<?php
$newValue = null;
$values = [];
$input = '123';
namespace Rector\CodeQuality\Tests\Rector\Foreach_\SimplifyForeachToCoalescingRector\Fixture;
$newValue = $values[$input] ?? $newValue;
class SimpleIdenticalForeach
{
public function run()
{
$newValue = null;
$values = [];
$input = '123';
$newValue = $values[$input] ?? $newValue;
$newValue = $values[$input] ?? $newValue;
$newValue = $values[$input] ?? $newValue;
}
}
?>

View File

@ -1,13 +0,0 @@
<?php
$array = [];
in_array('key', array_values($array), true);
?>
-----
<?php
$array = [];
in_array('key', $array, true);
?>

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Tests\Rector\FuncCall\SimplifyInArrayValuesRector\Fixture;
final class InArray
{
public function run()
{
$array = ['key', 'value'];
return in_array('key', array_values($array), true);
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Tests\Rector\FuncCall\SimplifyInArrayValuesRector\Fixture;
final class InArray
{
public function run()
{
$array = ['key', 'value'];
return in_array('key', $array, true);
}
}
?>

View File

@ -1,19 +0,0 @@
<?php
$string = 'hey';
strpos(strtolower($string), 'find-me');
$funcName = 'strpos';
$funcName(strtolower($string), 'find-me');
?>
-----
<?php
$string = 'hey';
stripos($string, 'find-me');
$funcName = 'strpos';
$funcName(strtolower($string), 'find-me');
?>

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Tests\Rector\FuncCall\SimplifyStrposLowerRector\Fixture;
final class StrposCalls
{
public function run()
{
$string = 'hey';
strpos(strtolower($string), 'find-me');
$funcName = 'strpos';
$funcName(strtolower($string), 'find-me');
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\CodeQuality\Tests\Rector\FuncCall\SimplifyStrposLowerRector\Fixture;
final class StrposCalls
{
public function run()
{
$string = 'hey';
stripos($string, 'find-me');
$funcName = 'strpos';
$funcName(strtolower($string), 'find-me');
}
}
?>

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Generic\Tests\Rector\Name\PseudoNamespaceToNamespaceRector\Source;
final class Keep_This
{
}

View File

@ -90,11 +90,11 @@ final class ClassRenamer
}
if ($node instanceof Namespace_) {
return $this->refactorNamespaceNode($node, $oldToNewClasses);
return $this->refactorNamespace($node, $oldToNewClasses);
}
if ($node instanceof ClassLike) {
return $this->refactorClassLikeNode($node, $oldToNewClasses);
return $this->refactorClassLike($node, $oldToNewClasses);
}
return null;
@ -149,7 +149,7 @@ final class ClassRenamer
return $name;
}
private function refactorNamespaceNode(Namespace_ $namespace, array $oldToNewClasses): ?Node
private function refactorNamespace(Namespace_ $namespace, array $oldToNewClasses): ?Node
{
$name = $this->nodeNameResolver->getName($namespace);
if ($name === null) {
@ -178,7 +178,7 @@ final class ClassRenamer
return $namespace;
}
private function refactorClassLikeNode(ClassLike $classLike, array $oldToNewClasses): ?Node
private function refactorClassLike(ClassLike $classLike, array $oldToNewClasses): ?Node
{
// rename interfaces
$this->renameClassImplements($classLike, $oldToNewClasses);

View File

@ -2,22 +2,19 @@
declare(strict_types=1);
namespace Rector\Generic\Rector\Name;
namespace Rector\Renaming\Rector\FileWithoutNamespace;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Use_;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\Manipulator\ClassInsertManipulator;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
@ -27,7 +24,7 @@ use Rector\NodeTypeResolver\PhpDoc\PhpDocTypeRenamer;
use Webmozart\Assert\Assert;
/**
* @see \Rector\Generic\Tests\Rector\Name\PseudoNamespaceToNamespaceRector\PseudoNamespaceToNamespaceRectorTest
* @see \Rector\Renaming\Tests\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector\PseudoNamespaceToNamespaceRectorTest
*/
final class PseudoNamespaceToNamespaceRector extends AbstractRector implements ConfigurableRectorInterface
{
@ -45,29 +42,21 @@ final class PseudoNamespaceToNamespaceRector extends AbstractRector implements C
/**
* @var PseudoNamespaceToNamespace[]
*/
private $namespacePrefixesWithExcludedClasses = [];
private $pseudoNamespacesToNamespaces = [];
/**
* @var PhpDocTypeRenamer
*/
private $phpDocTypeRenamer;
/**
* @var ClassInsertManipulator
*/
private $classInsertManipulator;
/**
* @var string|null
*/
private $newNamespace;
public function __construct(
ClassInsertManipulator $classInsertManipulator,
PhpDocTypeRenamer $phpDocTypeRenamer
) {
public function __construct(PhpDocTypeRenamer $phpDocTypeRenamer)
{
$this->phpDocTypeRenamer = $phpDocTypeRenamer;
$this->classInsertManipulator = $classInsertManipulator;
}
public function getDefinition(): RectorDefinition
@ -101,55 +90,83 @@ CODE_SAMPLE
public function getNodeTypes(): array
{
// property, method
return [Name::class, Identifier::class, Property::class, FunctionLike::class, Expression::class];
return [FileWithoutNamespace::class, Namespace_::class];
}
/**
* @param Name|Identifier|Property|FunctionLike $node
* @param Name|Identifier|Property|FunctionLike|FileWithoutNamespace|Namespace_ $node
*/
public function refactor(Node $node): ?Node
{
// replace on @var/@param/@return/@throws
foreach ($this->namespacePrefixesWithExcludedClasses as $namespacePrefixWithExcludedClasses) {
$this->phpDocTypeRenamer->changeUnderscoreType($node, $namespacePrefixWithExcludedClasses);
}
$this->newNamespace = null;
if ($node instanceof Name || $node instanceof Identifier) {
return $this->processNameOrIdentifier($node);
}
if ($node instanceof FileWithoutNamespace) {
$stmts = $this->refactorStmts((array) $node->stmts);
$node->stmts = $stmts;
return null;
}
// add a new namespace?
if ($this->newNamespace) {
$namespace = new Namespace_(new Name($this->newNamespace));
$namespace->stmts = $stmts;
/**
* @param Stmt[] $nodes
* @return Node[]
*/
public function afterTraverse(array $nodes): array
{
if ($this->newNamespace === null) {
return $nodes;
}
$namespace = new Namespace_(new Name($this->newNamespace));
foreach ($nodes as $key => $node) {
if ($node instanceof Use_ || $node instanceof Class_) {
$nodes = $this->classInsertManipulator->insertBefore($nodes, $namespace, $key);
break;
return $namespace;
}
}
$this->newNamespace = null;
if ($node instanceof Namespace_) {
$this->refactorStmts([$node]);
return $node;
}
return $nodes;
return null;
}
public function configure(array $configuration): void
{
$namespacePrefixesWithExcludedClasses = $configuration[self::NAMESPACE_PREFIXES_WITH_EXCLUDED_CLASSES] ?? [];
Assert::allIsInstanceOf($namespacePrefixesWithExcludedClasses, PseudoNamespaceToNamespace::class);
$this->namespacePrefixesWithExcludedClasses = $namespacePrefixesWithExcludedClasses;
$this->pseudoNamespacesToNamespaces = $namespacePrefixesWithExcludedClasses;
}
/**
* @param Node[] $nodes
* @return Node[]
*/
private function refactorStmts(array $nodes): array
{
$this->traverseNodesWithCallable($nodes, function (Node $node): ?Node {
if (! $this->isInstancesOf($node, [Name::class, Identifier::class, Property::class, FunctionLike::class])) {
return null;
}
// replace on @var/@param/@return/@throws
foreach ($this->pseudoNamespacesToNamespaces as $namespacePrefixWithExcludedClasses) {
$this->phpDocTypeRenamer->changeUnderscoreType($node, $namespacePrefixWithExcludedClasses);
}
if ($node instanceof Name || $node instanceof Identifier) {
return $this->processNameOrIdentifier($node);
}
return null;
});
return $nodes;
}
/**
* @param class-string[] $types
*/
private function isInstancesOf(Node $node, array $types): bool
{
foreach ($types as $type) {
if (is_a($node, $type, true)) {
return true;
}
}
return false;
}
/**
@ -163,12 +180,12 @@ CODE_SAMPLE
return null;
}
foreach ($this->namespacePrefixesWithExcludedClasses as $namespacePrefixWithExcludedClasses) {
if (! $this->isName($node, $namespacePrefixWithExcludedClasses->getNamespacePrefix() . '*')) {
foreach ($this->pseudoNamespacesToNamespaces as $pseudoNamespacesToNamespace) {
if (! $this->isName($node, $pseudoNamespacesToNamespace->getNamespacePrefix() . '*')) {
continue;
}
$excludedClasses = $namespacePrefixWithExcludedClasses->getExcludedClasses();
$excludedClasses = $pseudoNamespacesToNamespace->getExcludedClasses();
if (is_array($excludedClasses) && $this->isNames($node, $excludedClasses)) {
return null;
}

View File

@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use Rector\Core\Configuration\ChangeConfiguration;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
@ -100,6 +101,7 @@ CODE_SAMPLE
Expression::class,
ClassLike::class,
Namespace_::class,
FileWithoutNamespace::class,
];
}

View File

@ -17,7 +17,6 @@ class Rector_Generic_Tests_Rector_Name_PseudoNamespaceToNamespaceRector_Fixture_
namespace Rector\Generic\Tests\Rector\Name\PseudoNamespaceToNamespaceRector\Fixture;
use Rector\Generic\Tests\Rector\Name\PseudoNamespaceToNamespaceRector\Source\Keep_This;
class UseStatement
{
public function run()
@ -25,5 +24,3 @@ class UseStatement
return new Keep_This;
}
}
?>

View File

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace Rector\Generic\Tests\Rector\Name\PseudoNamespaceToNamespaceRector;
namespace Rector\Renaming\Tests\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Generic\Rector\Name\PseudoNamespaceToNamespaceRector;
use Rector\Generic\ValueObject\PseudoNamespaceToNamespace;
use Rector\Renaming\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector;
use Symplify\SmartFileSystem\SmartFileInfo;
final class PseudoNamespaceToNamespaceRectorTest extends AbstractRectorTestCase

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Rector\Renaming\Tests\Rector\FileWithoutNamespace\PseudoNamespaceToNamespaceRector\Source;
final class Keep_This
{
}

View File

@ -1,7 +1,5 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\TypehintAlreadyDefinedWithWrongPhpdocTypehint;
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\Php72;
/** @param object $foo */ function my_foo($foo) {}
@ -11,8 +9,6 @@ namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRe
-----
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\TypehintAlreadyDefinedWithWrongPhpdocTypehint;
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ParamTypeDeclarationRector\Fixture\PhpCsFixerParam\Php72;
/** @param object $foo */ function my_foo(object $foo) {}

View File

@ -1,11 +1,19 @@
<?php
/** @return mixed */
function test222($value) {
return $value;
}
namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\ReturnTypeDeclarationRector\Fixture\nikic;
/** @return static */
function test333($value) {
return $value;
class SkipMixedAndStaticOverFunction
{
public function run(): void
{
/** @return mixed */
function test222($value) {
return $value;
}
/** @return static */
function test333($value) {
return $value;
}
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Rector\Core\PhpParser\Node\CustomNode;
use PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* Inspired by https://github.com/phpstan/phpstan-src/commit/ed81c3ad0b9877e6122c79b4afda9d10f3994092
*/
final class FileWithoutNamespace extends NodeAbstract
{
/**
* @var Node[]
*/
public $stmts = [];
/**
* @param Node[] $stmts
*/
public function __construct(array $stmts)
{
$this->stmts = $stmts;
parent::__construct();
}
public function getType(): string
{
return 'FileWithoutNamespace';
}
/**
* @return string[]
*/
public function getSubNodeNames(): array
{
return ['stmts'];
}
/**
* @return Node[]
*/
public function getStmts(): array
{
return $this->stmts;
}
}

View File

@ -35,6 +35,8 @@ use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\Node\UnionType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\MixedType;
@ -407,6 +409,21 @@ final class NodeFactory
]);
}
/**
* @param string[] $names
* @return Use_[]
*/
public function createUsesFromNames(array $names): array
{
$uses = [];
foreach ($names as $resolvedName) {
$useUse = new UseUse(new Name($resolvedName));
$uses[] = new Use_([$useUse]);
}
return $uses;
}
/**
* @param mixed $item
* @param string|int|null $key

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace Rector\Core\PhpParser\NodeTraverser;
use PhpParser\Node;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\NodeFinder;
use PhpParser\NodeTraverser;
use Rector\Caching\Contract\Rector\ZeroCacheRectorInterface;
use Rector\Core\Application\ActiveRectorsProvider;
@ -12,8 +14,11 @@ use Rector\Core\Configuration\Configuration;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Contract\Rector\PhpRectorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\Testing\Application\EnabledRectorsProvider;
use Rector\Core\Testing\PHPUnit\StaticPHPUnitEnvironment;
use Rector\NodeTypeResolver\FileSystem\CurrentFileInfoProvider;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class RectorNodeTraverser extends NodeTraverser
{
@ -27,10 +32,22 @@ final class RectorNodeTraverser extends NodeTraverser
*/
private $enabledRectorsProvider;
/**
* @var NodeFinder
*/
private $nodeFinder;
/**
* @var CurrentFileInfoProvider
*/
private $currentFileInfoProvider;
public function __construct(
EnabledRectorsProvider $enabledRectorsProvider,
Configuration $configuration,
ActiveRectorsProvider $activeRectorsProvider
ActiveRectorsProvider $activeRectorsProvider,
NodeFinder $nodeFinder,
CurrentFileInfoProvider $currentFileInfoProvider
) {
/** @var PhpRectorInterface[] $phpRectors */
$phpRectors = $activeRectorsProvider->provideByType(PhpRectorInterface::class);
@ -45,6 +62,9 @@ final class RectorNodeTraverser extends NodeTraverser
$this->addVisitor($phpRector);
}
$this->nodeFinder = $nodeFinder;
$this->currentFileInfoProvider = $currentFileInfoProvider;
}
/**
@ -57,6 +77,16 @@ final class RectorNodeTraverser extends NodeTraverser
$this->configureEnabledRectorsOnly();
}
$hasNamespace = (bool) $this->nodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
if (! $hasNamespace) {
$fileWithoutNamespace = new FileWithoutNamespace($nodes);
$fileWithoutNamespace->setAttribute(
AttributeKey::FILE_INFO,
$this->currentFileInfoProvider->getSmartFileInfo()
);
return parent::traverse([$fileWithoutNamespace]);
}
return parent::traverse($nodes);
}

View File

@ -23,6 +23,7 @@ use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\PrettyPrinter\Standard;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Symplify\SmartFileSystem\SmartFileInfo;
@ -115,10 +116,12 @@ final class BetterStandardPrinter extends Standard
public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens): string
{
// detect per print
$this->detectTabOrSpaceIndentCharacter($stmts);
$newStmts = $this->resolveNewStmts($stmts);
$content = parent::printFormatPreserving($stmts, $origStmts, $origTokens);
// detect per print
$this->detectTabOrSpaceIndentCharacter($newStmts);
$content = parent::printFormatPreserving($newStmts, $origStmts, $origTokens);
// add new line in case of added stmts
if (count($stmts) !== count($origStmts) && ! (bool) Strings::match($content, self::NEWLINE_END_REGEX)) {
@ -189,6 +192,13 @@ final class BetterStandardPrinter extends Standard
return parent::prettyPrintFile($stmts) . PHP_EOL;
}
public function pFileWithoutNamespace(FileWithoutNamespace $fileWithoutNamespace): string
{
$content = self::pStmts((array) $fileWithoutNamespace->stmts, false);
return ltrim($content);
}
/**
* This allows to use both spaces and tabs vs. original space-only
*/
@ -424,6 +434,21 @@ final class BetterStandardPrinter extends Standard
return parent::pStmt_Use($use);
}
/**
* @return Node[]|mixed[]
*/
private function resolveNewStmts(array $stmts): array
{
if (count($stmts) === 1) {
$onlyStmt = $stmts[0];
if ($onlyStmt instanceof FileWithoutNamespace) {
return $onlyStmt->stmts;
}
}
return $stmts;
}
/**
* Solves https://github.com/rectorphp/rector/issues/1964
*

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Rector\Core\PhpParser\Printer;
use PhpParser\Node;
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\ValueObject\Application\ParsedStmtsAndTokens;
use Symplify\SmartFileSystem\SmartFileInfo;
use Symplify\SmartFileSystem\SmartFileSystem;
@ -57,8 +58,13 @@ final class FormatPerservingPrinter
public function printParsedStmstAndTokensToString(ParsedStmtsAndTokens $parsedStmtsAndTokens): string
{
return $this->betterStandardPrinter->printFormatPreserving($parsedStmtsAndTokens->getNewStmts(),
$parsedStmtsAndTokens->getOldStmts(), $parsedStmtsAndTokens->getOldTokens());
$newStmts = $this->resolveNewStmts($parsedStmtsAndTokens);
return $this->betterStandardPrinter->printFormatPreserving(
$newStmts,
$parsedStmtsAndTokens->getOldStmts(),
$parsedStmtsAndTokens->getOldTokens()
);
}
public function printParsedStmstAndTokens(
@ -72,4 +78,19 @@ final class FormatPerservingPrinter
$parsedStmtsAndTokens->getOldTokens()
);
}
/**
* @return Node[]
*/
private function resolveNewStmts(ParsedStmtsAndTokens $parsedStmtsAndTokens): array
{
if (count($parsedStmtsAndTokens->getNewStmts()) === 1) {
$onlyStmt = $parsedStmtsAndTokens->getNewStmts()[0];
if ($onlyStmt instanceof FileWithoutNamespace) {
return $onlyStmt->stmts;
}
}
return $parsedStmtsAndTokens->getNewStmts();
}
}