Skip to content

Commit

Permalink
Merge 4ecc66d into 9960874
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jan 11, 2020
2 parents 9960874 + 4ecc66d commit 3473dc8
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@
"tests/Rector/Namespace_/PseudoNamespaceToNamespaceRector/Source",
"tests/Issues/Issue1243/Source",
"packages/Autodiscovery/tests/Rector/FileSystem/MoveInterfacesToContractNamespaceDirectoryRector/Expected",
"packages/Autodiscovery/tests/Rector/FileSystem/MoveServicesBySuffixToDirectoryRector/Expected"
"packages/Autodiscovery/tests/Rector/FileSystem/MoveServicesBySuffixToDirectoryRector/Expected",
"packages/CakePHP/tests/Rector/StaticCall/AppUsesStaticCallToUseStatementRector/Source"
],
"files": [
"packages/DeadCode/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/Source/UserDefined.php",
Expand Down
2 changes: 2 additions & 0 deletions config/set/cakephp/cakephp30.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
services:
Rector\CakePHP\Rector\StaticCall\AppUsesStaticCallToUseStatementRector: null
9 changes: 9 additions & 0 deletions packages/CakePHP/config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
autowire: true
public: true

Rector\CakePHP\:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'
90 changes: 90 additions & 0 deletions packages/CakePHP/src/FullyQualifiedClassNameResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Rector\CakePHP;

use Nette\Utils\Strings;

/**
* @inspired https://github.com/cakephp/upgrade/blob/756410c8b7d5aff9daec3fa1fe750a3858d422ac/src/Shell/Task/AppUsesTask.php
*/
final class FullyQualifiedClassNameResolver
{
/**
* A map of old => new for use statements that are missing
*
* @var string[]
*/
public $implicitMap = [
'App' => 'Cake\Core\App',
'AppController' => 'App\Controller\AppController',
'AppHelper' => 'App\View\Helper\AppHelper',
'AppModel' => 'App\Model\AppModel',
'Cache' => 'Cake\Cache\Cache',
'CakeEventListener' => 'Cake\Event\EventListener',
'CakeLog' => 'Cake\Log\Log',
'CakePlugin' => 'Cake\Core\Plugin',
'CakeTestCase' => 'Cake\TestSuite\TestCase',
'CakeTestFixture' => 'Cake\TestSuite\Fixture\TestFixture',
'Component' => 'Cake\Controller\Component',
'ComponentRegistry' => 'Cake\Controller\ComponentRegistry',
'Configure' => 'Cake\Core\Configure',
'ConnectionManager' => 'Cake\Database\ConnectionManager',
'Controller' => 'Cake\Controller\Controller',
'Debugger' => 'Cake\Error\Debugger',
'ExceptionRenderer' => 'Cake\Error\ExceptionRenderer',
'Helper' => 'Cake\View\Helper',
'HelperRegistry' => 'Cake\View\HelperRegistry',
'Inflector' => 'Cake\Utility\Inflector',
'Model' => 'Cake\Model\Model',
'ModelBehavior' => 'Cake\Model\Behavior',
'Object' => 'Cake\Core\Object',
'Router' => 'Cake\Routing\Router',
'Shell' => 'Cake\Console\Shell',
'View' => 'Cake\View\View',
// Also apply to already renamed ones
'Log' => 'Cake\Log\Log',
'Plugin' => 'Cake\Core\Plugin',
'TestCase' => 'Cake\TestSuite\TestCase',
'TestFixture' => 'Cake\TestSuite\Fixture\TestFixture',
];

/**
* This value used to be directory
* So "/" in path should be "\" in namespace
*/
public function resolveFromPseudoNamespaceAndShortClassName(string $pseudoNamespace, string $shortClass): string
{
$pseudoNamespace = $this->normalizeFileSystemSlashes($pseudoNamespace);

// A. is knowinly renamed class?
if (isset($this->implicitMap[$shortClass])) {
return $this->implicitMap[$shortClass];
}

// Chop Lib out as locations moves those files to the top level.
// But only if Lib is not the last folder.
if (Strings::match($pseudoNamespace, '#\\\\Lib\\\\#')) {
$pseudoNamespace = Strings::replace($pseudoNamespace, '#\\\\Lib#');
}

// B. is Cake native class?
$cakePhpVersion = 'Cake\\' . $pseudoNamespace . '\\' . $shortClass;
if (class_exists($cakePhpVersion) || interface_exists($cakePhpVersion)) {
return $cakePhpVersion;
}

// C. is not plugin nor lib custom App class?
if (Strings::contains($pseudoNamespace, '\\') && ! Strings::match($pseudoNamespace, '#(Plugin|Lib)#')) {
return 'App\\' . $pseudoNamespace . '\\' . $shortClass;
}

return $pseudoNamespace . '\\' . $shortClass;
}

private function normalizeFileSystemSlashes(string $pseudoNamespace): string
{
return Strings::replace($pseudoNamespace, '#(/|\.)#', '\\');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

declare(strict_types=1);

namespace Rector\CakePHP\Rector\StaticCall;

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\FullyQualifiedClassNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPStan\Type\FullyQualifiedObjectType;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\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\StaticCall\AppUsesStaticCallToUseStatementRector\AppUsesStaticCallToUseStatementRectorTest
*/
final class AppUsesStaticCallToUseStatementRector extends AbstractRector
{
/**
* @var FullyQualifiedClassNameResolver
*/
private $fullyQualifiedClassNameResolver;

public function __construct(FullyQualifiedClassNameResolver $fullyQualifiedClassNameResolver)
{
$this->fullyQualifiedClassNameResolver = $fullyQualifiedClassNameResolver;
}

public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change App::uses() to use imports', [
new CodeSample(
<<<'PHP'
App::uses('NotificationListener', 'Event');
CakeEventManager::instance()->attach(new NotificationListener());
PHP
,
<<<'PHP'
use Event\NotificationListener;
CakeEventManager::instance()->attach(new NotificationListener());
PHP

),
]);
}

/**
* @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 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->fullyQualifiedClassNameResolver->resolveFromPseudoNamespaceAndShortClassName(
$namespaceName,
$shortClassName
);
}

private function createUseFromFullyQualifiedName(string $fullyQualifiedName): Use_
{
$useUse = new UseUse(new Name($fullyQualifiedName));

return new Use_([$useUse]);
}

private function isAppUses($staticCall): bool
{
if (! $this->isName($staticCall->class, 'App')) {
return false;
}

return $this->isName($staticCall->name, 'uses');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector;

use Iterator;
use Rector\CakePHP\Rector\StaticCall\AppUsesStaticCallToUseStatementRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class AppUsesStaticCallToUseStatementRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}

public function provideDataForTest(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

protected function getRectorClass(): string
{
return AppUsesStaticCallToUseStatementRector::class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

\App::uses('Component', 'Controller');

class CakeController
{
}

?>
-----
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

use Cake\Controller\Component;

class CakeController
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

\App::uses('HttpSocket', 'Network/Http');
\App::uses('Xml', 'Utility');
\App::uses('Component', 'Controller');
\App::uses('SomeLib', 'Data.Lib');
\App::uses('CurrencyLib', 'PluginName.Lib/Currency');
\App::uses('FooShell', 'MyPlugin.Console/Command');

// https://github.com/cakephp/upgrade/blob/05d85c147bb1302b576b818cabb66a40462aaed0/tests/test_files/AppUsesAfter.php
class CakePhpFixture
{
}

?>
-----
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

use App\Network\Http\HttpSocket;
use Cake\Utility\Xml;
use Cake\Controller\Component;
use Data\Lib\SomeLib;
use PluginName\Currency\CurrencyLib;
use MyPlugin\Console\Command\FooShell;

// https://github.com/cakephp/upgrade/blob/05d85c147bb1302b576b818cabb66a40462aaed0/tests/test_files/AppUsesAfter.php
class CakePhpFixture
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

class ImportNamespacesUp
{
public function test()
{
\App::uses('HtmlDomLib', 'Foo.Lib');
$HtmlDom = new HtmlDomLib();
\App::uses('HtmlDomLibExt', 'Foo.Lib');
$HtmlDom = new HtmlDomLibExt();
}
}

?>
-----
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

use Foo\Lib\HtmlDomLib;
use Foo\Lib\HtmlDomLibExt;
class ImportNamespacesUp
{
public function test()
{
$HtmlDom = new HtmlDomLib();
$HtmlDom = new HtmlDomLibExt();
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

\App::uses('NotificationListener', 'Event');

class SomeClass
{
public function run()
{
$values = new NotificationListener();
}
}

?>
-----
<?php

namespace Rector\CakePHP\Tests\Rector\StaticCall\AppUsesStaticCallToUseStatementRector\Fixture;

use Event\NotificationListener;

class SomeClass
{
public function run()
{
$values = new NotificationListener();
}
}

?>

0 comments on commit 3473dc8

Please sign in to comment.