[Legacy] Add FunctionToStaticMethodRector

This commit is contained in:
TomasVotruba 2020-05-12 17:20:40 +02:00
parent 310ae4a580
commit 0c67709fd0
27 changed files with 376 additions and 22 deletions

View File

@ -1,4 +1,4 @@
# All 489 Rectors Overview
# All 490 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -22,7 +22,7 @@
- [Guzzle](#guzzle) (1)
- [JMS](#jms) (2)
- [Laravel](#laravel) (6)
- [Legacy](#legacy) (1)
- [Legacy](#legacy) (2)
- [MysqlToMysqli](#mysqltomysqli) (4)
- [Naming](#naming) (1)
- [Nette](#nette) (11)
@ -4145,6 +4145,27 @@ Change singleton class to normal class that can be registered as a service
<br>
### `FunctionToStaticMethodRector`
- class: [`Rector\Legacy\Rector\Node\FunctionToStaticMethodRector`](/../master/rules/legacy/src/Rector/Node/FunctionToStaticMethodRector.php)
Change functions to static calls, so composer can autoload them
```diff
-function some_function()
+class SomeUtilsClass
{
+ public static function someFunction()
+ {
+ }
}
-some_function('lol');
+SomeUtilsClass::someFunction('lol');
```
<br>
## MysqlToMysqli
### `MysqlAssignToMysqliRector`

View File

@ -1,3 +1,7 @@
services:
SlevomatCodingStandard\Sniffs\Commenting\DisallowCommentAfterCodeSniff: null
SlevomatCodingStandard\Sniffs\Whitespaces\DuplicateSpacesSniff: null
parameters:
paths:
- "bin"

View File

@ -171,7 +171,8 @@ final class ParsedFunctionLikeNodeCollector
private function addMethod(ClassMethod $classMethod): void
{
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) { // anonymous
// anonymous
if ($className === null) {
return;
}
@ -192,7 +193,8 @@ final class ParsedFunctionLikeNodeCollector
$classType = $this->resolveNodeClassTypes($node->class);
}
if ($classType instanceof MixedType) { // anonymous
// anonymous
if ($classType instanceof MixedType) {
return;
}

View File

@ -30,7 +30,8 @@ final class MethodCallParsedNodesFinder
{
/** @var string|null $className */
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) { // anonymous
// anonymous
if ($className === null) {
return [];
}

View File

@ -134,7 +134,8 @@ final class NodeScopeAndMetadataDecorator
$nodes = $nodeTraverser->traverse($nodes);
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor($this->cloningVisitor); // needed also for format preserving printing
// needed also for format preserving printing
$nodeTraverser->addVisitor($this->cloningVisitor);
$nodeTraverser->addVisitor($this->parentAndNextNodeVisitor);
$nodeTraverser->addVisitor($this->functionMethodAndClassNodeVisitor);
$nodeTraverser->addVisitor($this->namespaceNodeVisitor);

View File

@ -69,7 +69,7 @@ final class TypeComparator
}
/**
* E.g. class A extends B, class B A[] is subtype of B[] keep A[]
* E.g. class A extends B, class B A[] is subtype of B[] keep A[]
*/
private function areArrayTypeWithSingleObjectChildToParent(Type $firstType, Type $secondType): bool
{

View File

@ -77,7 +77,8 @@ PHP
}
$onlyArrayItem = $arrayNode->items[0]->value;
if (isset($node->args[2])) { // strict
// strict
if (isset($node->args[2])) {
return new Identical($node->args[0]->value, $onlyArrayItem);
}

View File

@ -7,8 +7,11 @@ namespace Rector\CodingStyle\Naming;
use Nette\Utils\Strings;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Function_;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Util\StaticRectorStrings;
use Rector\NodeNameResolver\NodeNameResolver;
use Symplify\SmartFileSystem\SmartFileInfo;
final class ClassNaming
{
@ -45,4 +48,20 @@ final class ClassNaming
return Strings::before($fullyQualifiedName, '\\', -1) ?: null;
}
public function getNameFromFileInfo(SmartFileInfo $smartFileInfo): string
{
$basename = $smartFileInfo->getBasenameWithoutSuffix();
return StaticRectorStrings::underscoreToCamelCase($basename);
}
/**
* "some_function" "someFunction"
*/
public function createMethodNameFromFunction(Function_ $function): string
{
$functionName = (string) $function->name;
return StaticRectorStrings::underscoreToPascalCase($functionName);
}
}

View File

@ -152,7 +152,8 @@ PHP
/** @var string|null $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) { // anonymous class
// anonymous class
if ($className === null) {
return [];
}

View File

@ -94,7 +94,8 @@ PHP
{
return [
'Illuminate\Support\Facades\Cache' => [
'put' => 2, // time argument position
// time argument position
'put' => 2,
'add' => 2,
],
Store::class => [

View File

@ -7,3 +7,4 @@ services:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'
- '../src/ValueObject/*'

View File

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace Rector\Legacy\Rector\Node;
use PhpParser\Builder\Class_ as ClassBuilder;
use PhpParser\Builder\Method;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Namespace_;
use Rector\CodingStyle\Naming\ClassNaming;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\FileSystemRector\Rector\AbstractFileSystemRector;
use Rector\Legacy\ValueObject\StaticCallPointer;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @see \Rector\Legacy\Tests\Rector\FileSystem\FunctionToStaticMethodRector\FunctionToStaticMethodRectorTest
*/
final class FunctionToStaticMethodRector extends AbstractFileSystemRector
{
/**
* @var ClassNaming
*/
private $classNaming;
/**
* @var StaticCallPointer[]
*/
private $functionNameToClassStaticMethod = [];
public function __construct(ClassNaming $classNaming)
{
$this->classNaming = $classNaming;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change functions to static calls, so composer can autoload them', [
new CodeSample(
<<<'PHP'
function some_function()
{
}
some_function('lol');
PHP
,
<<<'PHP'
class SomeUtilsClass
{
public static function someFunction()
{
}
}
SomeUtilsClass::someFunction('lol');
PHP
),
]);
}
public function refactor(SmartFileInfo $smartFileInfo): void
{
$nodes = $this->parseFileInfoToNodes($smartFileInfo);
$fileStmts = $this->getFileOrNamespaceStmts($nodes);
/** @var Function_[] $functions */
$functions = $this->betterNodeFinder->findInstanceOf($fileStmts, Function_::class);
if ($functions === []) {
return;
}
$shortClassName = $this->classNaming->getNameFromFileInfo($smartFileInfo);
$classBuilder = new ClassBuilder($shortClassName);
$classBuilder->makeFinal();
$className = $this->getFullyQualifiedName($nodes, $shortClassName);
foreach ($functions as $function) {
$functionName = $this->getName($function);
$methodName = $this->classNaming->createMethodNameFromFunction($function);
$this->functionNameToClassStaticMethod[$functionName] = new StaticCallPointer($className, $methodName);
$staticClassMethod = $this->createClassMethodFromFunction($methodName, $function);
$classBuilder->addStmt($staticClassMethod);
// remove after convert, we won't need it
$this->removeNode($function);
}
$class = $classBuilder->getNode();
$classFilePath = $smartFileInfo->getPath() . DIRECTORY_SEPARATOR . $shortClassName . '.php';
$nodesToPrint = $this->resolveNodesToPrint($nodes, $class);
// replace function calls with class static call
$this->traverseNodesWithCallable($nodes, function (Node $node) {
if (! $node instanceof FuncCall) {
return null;
}
$funcCallName = $this->getName($node);
$staticCallPointer = $this->functionNameToClassStaticMethod[$funcCallName] ?? null;
if ($staticCallPointer === null) {
return null;
}
$staticCall = $this->createStaticCall($staticCallPointer->getClass(), $staticCallPointer->getMethod());
$staticCall->args = $node->args;
return $staticCall;
});
// @todo decouple to PostRectorInterface, so it's covered in external files too
$this->printNewNodesToFilePath($nodesToPrint, $classFilePath);
}
/**
* @param Node[] $nodes
* @return Node[]
*/
private function resolveNodesToPrint(array $nodes, Class_ $class): array
{
/** @var Namespace_|null $namespace */
$namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
if ($namespace !== null) {
// put class first
$namespace->stmts = array_merge([$class], $namespace->stmts);
return [$namespace];
}
return [$class];
}
/**
* @param Node[] $nodes
* @return Node[]
*/
private function getFileOrNamespaceStmts(array $nodes): array
{
/** @var Namespace_|null $namespace */
$namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
if ($namespace === null) {
return $nodes;
}
return $namespace->stmts;
}
private function getFullyQualifiedName(array $nodes, string $shortClassName): string
{
/** @var Namespace_|null $namespace */
$namespace = $this->betterNodeFinder->findFirstInstanceOf($nodes, Namespace_::class);
if ($namespace === null) {
return $shortClassName;
}
$namespaceName = $this->getName($namespace);
if ($namespaceName === null) {
return $shortClassName;
}
return $namespaceName . '\\' . $shortClassName;
}
private function createClassMethodFromFunction(string $methodName, Function_ $function): ClassMethod
{
$methodBuilder = new Method($methodName);
$methodBuilder->makePublic();
$methodBuilder->makeStatic();
$methodBuilder->addStmts($function->stmts);
return $methodBuilder->getNode();
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Rector\Legacy\ValueObject;
final class StaticCallPointer
{
/**
* @var string
*/
private $class;
/**
* @var string
*/
private $method;
public function __construct(string $class, string $method)
{
$this->class = $class;
$this->method = $method;
}
public function getClass(): string
{
return $this->class;
}
public function getMethod(): string
{
return $this->method;
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Rector\Legacy\Tests\Rector\FileSystem\FunctionToStaticMethodRector;
use Rector\Core\Testing\PHPUnit\AbstractFileSystemRectorTestCase;
use Rector\Legacy\Rector\Node\FunctionToStaticMethodRector;
final class FunctionToStaticMethodRectorTest extends AbstractFileSystemRectorTestCase
{
public function test(): void
{
$this->doTestFile(__DIR__ . '/Source/static_functions.php');
$this->assertFileExists(__DIR__ . '/Fixture/StaticFunctions.php');
$this->assertFileEquals(
__DIR__ . '/Source/ExpectedStaticFunctions.php',
__DIR__ . '/Fixture/StaticFunctions.php'
);
}
protected function getRectorClass(): string
{
return FunctionToStaticMethodRector::class;
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Rector\Legacy\Tests\Rector\FileSystem\FunctionToStaticMethodRector\Source;
final class StaticFunctions
{
public static function firstStaticFunction()
{
return 5;
}
}
$value = \Rector\Legacy\Tests\Rector\FileSystem\FunctionToStaticMethodRector\Source\StaticFunctions::firstStaticFunction();

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\Legacy\Tests\Rector\FileSystem\FunctionToStaticMethodRector\Source;
function first_static_function()
{
return 5;
}
$value = first_static_function();

View File

@ -42,7 +42,8 @@ final class StringClassNameToClassConstantRector extends AbstractRector
* @param string[] $classesToSkip
*/
public function __construct(array $classesToSkip = [
'Error', // can be string
// can be string
'Error',
])
{
$this->classesToSkip = $classesToSkip;

View File

@ -28,7 +28,8 @@ final class EregToPcreTransformer
':lower:' => '[:lower:]',
':print:' => '[:print:]',
':punct:' => '[:punct:]',
':space:' => '\013\s', // should include VT
// should include VT
':space:' => '\013\s',
':upper:' => '[:upper:]',
':xdigit:' => '[:xdigit:]',
];
@ -153,7 +154,8 @@ final class EregToPcreTransformer
throw new InvalidEregException('an invalid escape sequence at the end');
}
$r[$rr] .= $this->_ere2pcre_escape($content[$i]);
} else { // including ] and } which are allowed as a literal character
} else {
// including ] and } which are allowed as a literal character
$r[$rr] .= $this->_ere2pcre_escape($char);
}
++$i;
@ -181,7 +183,8 @@ final class EregToPcreTransformer
private function processBracket(string $content, int $i, int $l, array &$r, int $rr)
{
if ($i + 1 < $l && $content[$i + 1] === ')') { // special case
// special case
if ($i + 1 < $l && $content[$i + 1] === ')') {
$r[$rr] .= '()';
++$i;
} else {

View File

@ -113,7 +113,8 @@ PHP
private function createMethodCallExpressionFromTag(PhpDocTagNode $phpDocTagNode, string $method): MethodCall
{
$annotationContent = (string) $phpDocTagNode->value;
$annotationContent = ltrim($annotationContent, '\\'); // this is needed due to BuilderHelpers
// this is needed due to BuilderHelpers
$annotationContent = ltrim($annotationContent, '\\');
return $this->createMethodCall('this', $method, [$annotationContent]);
}

View File

@ -69,7 +69,8 @@ PHP
}
if ($this->isName($node->var->name, 'disableOriginalConstructor')) {
$getMockBuilderMethodCall = $node->var->var; // null;
// null;
$getMockBuilderMethodCall = $node->var->var;
} else {
$getMockBuilderMethodCall = $node->var;
}

View File

@ -8,6 +8,7 @@ use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\Php\TypeAnalyzer;
use Rector\Core\Yaml\YamlPrinter;
use Rector\PostRector\Contract\Rector\PostRectorInterface;
use Rector\Utils\DoctrineAnnotationParserSyncer\Contract\Rector\ClassSyncerRectorInterface;
use ReflectionClass;
use ReflectionNamedType;
use Symfony\Component\Console\Input\InputInterface;
@ -129,6 +130,11 @@ final class ShowCommand extends AbstractCommand
sort($rectors);
return array_filter($rectors, function (RectorInterface $rector) {
// utils rules
if ($rector instanceof ClassSyncerRectorInterface) {
return false;
}
// skip as internal and always run
return ! $rector instanceof PostRectorInterface;
});

View File

@ -19,9 +19,11 @@ final class FilesystemTweaker
{
$absoluteDirectories = [];
foreach ($directories as $directory) {
if (Strings::contains($directory, '*')) { // is fnmatch for directories
// is fnmatch for directories
if (Strings::contains($directory, '*')) {
$absoluteDirectories = array_merge($absoluteDirectories, glob($directory, GLOB_ONLYDIR));
} else { // is classic directory
} else {
// is classic directory
$this->ensureDirectoryExists($directory);
$absoluteDirectories[] = $directory;
}

View File

@ -32,7 +32,8 @@ final class PhpVersionProvider
// for tests
if (StaticPHPUnitEnvironment::isPHPUnitRun()) {
return '10.0'; // so we don't have to up
// so we don't have to up
return '10.0';
}
// see https://getcomposer.org/doc/06-config.md#platform

View File

@ -179,7 +179,8 @@ PHP
$methodCallsToAdd = array_reverse($methodCallsToAdd);
foreach ($methodCallsToAdd as $methodCallToAdd) {
$fluentMethodCall->var = new MethodCall( // make var a parent method call
// make var a parent method call
$fluentMethodCall->var = new MethodCall(
$fluentMethodCall->var,
$methodCallToAdd->name,
$methodCallToAdd->args

View File

@ -181,7 +181,8 @@ PHP
/** @var Identifier $identifierNode */
$identifierNode = $propertyFetch->name;
return $propertyToMethods[$identifierNode->toString()]; //[$type];
//[$type];
return $propertyToMethods[$identifierNode->toString()];
}
return null;

View File

@ -8,6 +8,7 @@ use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesProcessor;
use Rector\Core\Configuration\Configuration;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\HttpKernel\RectorKernel;
use Rector\FileSystemRector\Contract\FileSystemRectorInterface;
use Rector\FileSystemRector\FileSystemFileProcessor;
@ -56,6 +57,15 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes
{
$temporaryFilePath = $this->createTemporaryFilePathFromFilePath($file);
if ($temporaryFilePath === $file) {
$message = sprintf(
'File %s is about to be copied to itself. Move it out of "/Fixture" directory to "/Source"',
$file
);
throw new ShouldNotHappenException($message);
}
$this->fileSystemFileProcessor->processFileInfo(new SmartFileInfo($temporaryFilePath));
$this->removedAndAddedFilesProcessor->run();
@ -133,7 +143,7 @@ abstract class AbstractFileSystemRectorTestCase extends AbstractGenericRectorTes
$fileInfo->getBasename()
);
FileSystem::copy($file, $temporaryFilePath);
FileSystem::copy($file, $temporaryFilePath, true);
return $temporaryFilePath;
}

View File

@ -40,6 +40,13 @@ final class StaticRectorStrings
return self::camelCaseToGlue($input, '_');
}
public static function underscoreToPascalCase(string $string): string
{
$string = self::underscoreToCamelCase($string);
return lcfirst($string);
}
public static function underscoreToCamelCase(string $input): string
{
$nameParts = explode('_', $input);