Skip to content

Commit

Permalink
[Transform] Add FileGetContentsAndJsonDecodeToStaticCallRector (#2059)
Browse files Browse the repository at this point in the history
Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
TomasVotruba and actions-user committed Apr 11, 2022
1 parent 05f07aa commit 1fc2825
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 1 deletion.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"require-dev": {
"brianium/paratest": "^6.3",
"friendsofphp/php-cs-fixer": "^3.6",
"friendsofphp/php-cs-fixer": "^3.8",
"nategood/httpful": "^0.3.2",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-nette": "^1.0",
Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,5 @@ parameters:

# foreaching all stmts
- '#Cognitive complexity for "Rector\\CodeQuality\\Rector\\FunctionLike\\SimplifyUselessVariableRector\:\:refactor\(\)" is 1\d+, keep it under 10#'
- '#Method "processStmt\(\)" returns bool type, so the name should start with is/has/was#'
- '#Method "refactorReturnAndAssign\(\)" returns bool type, so the name should start with is/has/was#'
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Transform\Rector\FunctionLike\FileGetContentsAndJsonDecodeToStaticCallRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

final class FileGetContentsAndJsonDecodeToStaticCallRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}

/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Rector\Tests\Transform\Rector\FunctionLike\FileGetContentsAndJsonDecodeToStaticCallRector\Fixture;

final class AssignOfAssign
{
public function load($filePath)
{
$fileGetContents = file_get_contents($filePath);
$anotherValue = json_decode($fileGetContents, true);

return $anotherValue;
}
}

?>
-----
<?php

namespace Rector\Tests\Transform\Rector\FunctionLike\FileGetContentsAndJsonDecodeToStaticCallRector\Fixture;

final class AssignOfAssign
{
public function load($filePath)
{
$anotherValue = \FileLoader::loadJson($filePath);

return $anotherValue;
}
}

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

namespace Rector\Tests\Transform\Rector\FunctionLike\FileGetContentsAndJsonDecodeToStaticCallRector\Fixture;

final class ReturnOfFunction
{
public function load($filePath)
{
$fileGetContents = file_get_contents($filePath);
return json_decode($fileGetContents, true);
}
}

?>
-----
<?php

namespace Rector\Tests\Transform\Rector\FunctionLike\FileGetContentsAndJsonDecodeToStaticCallRector\Fixture;

final class ReturnOfFunction
{
public function load($filePath)
{
return \FileLoader::loadJson($filePath);
}
}

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

declare(strict_types=1);

use Rector\Config\RectorConfig;

use Rector\Transform\Rector\FunctionLike\FileGetContentsAndJsonDecodeToStaticCallRector;
use Rector\Transform\ValueObject\StaticCallRecipe;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->ruleWithConfiguration(
FileGetContentsAndJsonDecodeToStaticCallRector::class,
[new StaticCallRecipe('FileLoader', 'loadJson')]
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php

declare(strict_types=1);

namespace Rector\Transform\Rector\FunctionLike;

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Transform\ValueObject\StaticCallRecipe;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;

/**
* @see \Rector\Tests\Transform\Rector\FunctionLike\FileGetContentsAndJsonDecodeToStaticCallRector\FileGetContentsAndJsonDecodeToStaticCallRectorTest
*/
final class FileGetContentsAndJsonDecodeToStaticCallRector extends AbstractRector implements ConfigurableRectorInterface
{
private StaticCallRecipe $staticCallRecipe;

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Merge 2 function calls to static call', [
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
final class SomeClass
{
public function load($filePath)
{
$fileGetContents = file_get_contents($filePath);
return json_decode($fileGetContents, true);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeClass
{
public function load($filePath)
{
return FileLoader::loadJson($filePath);
}
}
CODE_SAMPLE
,
[new StaticCallRecipe('FileLoader', 'loadJson')]
),
]);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [FunctionLike::class];
}

/**
* @param FunctionLike $node
*/
public function refactor(Node $node): ?Node
{
$stmts = $node->getStmts();
if ($stmts === null) {
return null;
}

$hasChanged = false;

$previousStmt = null;
foreach ($stmts as $stmt) {
if ($this->processStmt($previousStmt, $stmt)) {
$hasChanged = true;
/** @var Stmt $previousStmt */
$this->removeNode($previousStmt);
}

$previousStmt = $stmt;
}

if ($hasChanged) {
return $node;
}

return null;
}

/**
* @param mixed[] $configuration
*/
public function configure(array $configuration): void
{
$staticCallRecipe = $configuration[0] ?? null;

Assert::isInstanceOf($staticCallRecipe, StaticCallRecipe::class);
$this->staticCallRecipe = $staticCallRecipe;
}

private function createStaticCall(FuncCall $fileGetContentsFuncCall): StaticCall
{
$fullyQualified = new FullyQualified($this->staticCallRecipe->getClassName());

return new StaticCall(
$fullyQualified,
$this->staticCallRecipe->getMethodName(),
$fileGetContentsFuncCall->getArgs()
);
}

private function processStmt(?Stmt $previousStmt, Stmt $currentStmt): bool
{
if (! $previousStmt instanceof Expression) {
return false;
}

$previousExpr = $previousStmt->expr;
if (! $previousExpr instanceof Assign) {
return false;
}

$previousAssign = $previousExpr;
if (! $previousAssign->expr instanceof FuncCall) {
return false;
}

if (! $this->isName($previousAssign->expr, 'file_get_contents')) {
return false;
}

$fileGetContentsFuncCall = $previousAssign->expr;

if ($currentStmt instanceof Return_) {
return $this->refactorReturnAndAssign($currentStmt, $fileGetContentsFuncCall);
}

if (! $currentStmt instanceof Expression) {
return false;
}

if (! $currentStmt->expr instanceof Assign) {
return false;
}

return $this->refactorReturnAndAssign($currentStmt->expr, $fileGetContentsFuncCall);
}

private function refactorReturnAndAssign(Return_|Assign $currentStmt, FuncCall $fileGetContentsFuncCall): bool
{
if (! $currentStmt->expr instanceof FuncCall) {
return false;
}

if (! $this->isName($currentStmt->expr, 'json_decode')) {
return false;
}

$currentStmt->expr = $this->createStaticCall($fileGetContentsFuncCall);
return true;
}
}
24 changes: 24 additions & 0 deletions rules/Transform/ValueObject/StaticCallRecipe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Rector\Transform\ValueObject;

final class StaticCallRecipe
{
public function __construct(
private readonly string $className,
private readonly string $methodName,
) {
}

public function getClassName(): string
{
return $this->className;
}

public function getMethodName(): string
{
return $this->methodName;
}
}

0 comments on commit 1fc2825

Please sign in to comment.