mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-01 17:00:51 +00:00
improve CreateFunctionToAnonymousFunctionRector
This commit is contained in:
parent
6ed11cc2ef
commit
605419a0d4
|
@ -4,10 +4,15 @@ namespace Rector\Php\Rector\FuncCall;
|
|||
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\ClosureUse;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Scalar\Encapsed;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
|
@ -89,12 +94,12 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
/** @var Variable[] $parameters */
|
||||
$parameters = $this->parseStringToNodes($node->args[0]->value);
|
||||
$body = $this->parseStringToNodes($node->args[1]->value);
|
||||
|
||||
$parameters = $this->parseStringToParameters($node->args[0]->value);
|
||||
$body = $this->parseStringToBody($node->args[1]->value);
|
||||
$useVariables = $this->resolveUseVariables($body, $parameters);
|
||||
|
||||
$anonymousFunctionNode = new Closure();
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
/** @var Variable $parameter */
|
||||
$anonymousFunctionNode->params[] = new Param($parameter);
|
||||
|
@ -112,41 +117,34 @@ CODE_SAMPLE
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string|String_|Node $content
|
||||
* @return Node[]|Variable[]|Stmt[]
|
||||
* @param String_|Expr $content
|
||||
* @return Stmt[]
|
||||
*/
|
||||
private function parseStringToNodes($content): array
|
||||
private function parseStringToBody(Node $content): array
|
||||
{
|
||||
if ($content instanceof String_) {
|
||||
$content = $content->value;
|
||||
if (! $content instanceof String_ && ! $content instanceof Encapsed && ! $content instanceof Concat) {
|
||||
// special case of code elsewhere
|
||||
return [$this->createEval($content)];
|
||||
}
|
||||
|
||||
if ($content instanceof Encapsed) {
|
||||
// remove "
|
||||
$content = trim($this->print($content), '""');
|
||||
// use \$ → $
|
||||
$content = Strings::replace($content, '#\\\\\$#', '$');
|
||||
// use \'{$...}\' → $...
|
||||
$content = Strings::replace($content, '#\'{(\$.*?)}\'#', '$1');
|
||||
}
|
||||
$content = $this->stringify($content);
|
||||
$content = Strings::endsWith($content, ';') ? $content : $content . ';';
|
||||
|
||||
if (! is_string($content)) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
return (array) $this->parser->parse('<?php ' . $content);
|
||||
}
|
||||
|
||||
$wrappedCode = '<?php ' . $content . (Strings::endsWith($content, ';') ? '' : ';');
|
||||
/**
|
||||
* @return Param[]
|
||||
*/
|
||||
private function parseStringToParameters(Expr $expr): array
|
||||
{
|
||||
$content = $this->stringify($expr);
|
||||
|
||||
$nodes = $this->parser->parse($wrappedCode);
|
||||
if (count($nodes) === 1) {
|
||||
if ($nodes[0] instanceof Expression) {
|
||||
return [$nodes[0]->expr];
|
||||
}
|
||||
$content = '<?php $value = function(' . $content . ') {};';
|
||||
|
||||
return [$nodes[0]];
|
||||
}
|
||||
$nodes = $this->parser->parse($content);
|
||||
|
||||
// @todo implement
|
||||
throw new ShouldNotHappenException();
|
||||
return $nodes[0]->expr->expr->params;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,15 +161,62 @@ CODE_SAMPLE
|
|||
|
||||
/** @var Variable[] $variableNodes */
|
||||
$variableNodes = $this->betterNodeFinder->findInstanceOf($nodes, Variable::class);
|
||||
foreach ($variableNodes as $i => $variableNode) {
|
||||
if (! in_array($this->getName($variableNode), $paramNames, true)) {
|
||||
|
||||
$filteredVariables = [];
|
||||
foreach ($variableNodes as $variableNode) {
|
||||
// "$this" is allowed
|
||||
if ($this->isName($variableNode, 'this')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($variableNodes[$i]);
|
||||
if (in_array($this->getName($variableNode), $paramNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filteredVariables[$this->getName($variableNode)] = $variableNode;
|
||||
}
|
||||
|
||||
// re-index
|
||||
return array_values($variableNodes);
|
||||
return array_values($filteredVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|Node $content
|
||||
*/
|
||||
private function stringify($content): string
|
||||
{
|
||||
if (is_string($content)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if ($content instanceof String_) {
|
||||
return $content->value;
|
||||
}
|
||||
|
||||
if ($content instanceof Encapsed) {
|
||||
// remove "
|
||||
$content = trim($this->print($content), '""');
|
||||
// use \$ → $
|
||||
$content = Strings::replace($content, '#\\\\\$#', '$');
|
||||
// use \'{$...}\' → $...
|
||||
return Strings::replace($content, '#\'{(\$.*?)}\'#', '$1');
|
||||
}
|
||||
|
||||
if ($content instanceof Concat) {
|
||||
return $this->stringify($content->left) . $this->stringify($content->right);
|
||||
}
|
||||
|
||||
if ($content instanceof Variable || $content instanceof PropertyFetch) {
|
||||
return $this->print($content);
|
||||
}
|
||||
|
||||
throw new ShouldNotHappenException(get_class($content) . ' ' . __METHOD__);
|
||||
}
|
||||
|
||||
private function createEval(Expr $node): Expression
|
||||
{
|
||||
$evalFuncCall = new FuncCall(new Name('eval'), [new Arg($node)]);
|
||||
|
||||
return new Expression($evalFuncCall);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,14 @@ final class CreateFunctionToAnonymousFunctionRectorTest extends AbstractRectorTe
|
|||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFiles([__DIR__ . '/Fixture/fixture.php.inc']);
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/fixture.php.inc',
|
||||
__DIR__ . '/Fixture/concat.php.inc',
|
||||
__DIR__ . '/Fixture/reference.php.inc',
|
||||
__DIR__ . '/Fixture/stackoverflow.php.inc',
|
||||
__DIR__ . '/Fixture/drupal.php.inc',
|
||||
__DIR__ . '/Fixture/php_net.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunctionWithConcat
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$value = 1;
|
||||
$callback = create_function( '$m', 'return $m->meta_id == '.$value.';');
|
||||
|
||||
$callback = create_function('$a', 'return "<cas:proxy>$a</cas:proxy>";');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunctionWithConcat
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$value = 1;
|
||||
$callback = function ($m) use ($value) {
|
||||
return $m->meta_id == $value;
|
||||
};
|
||||
|
||||
$callback = function ($a) {
|
||||
return "<cas:proxy>{$a}</cas:proxy>";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class Drupal
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$replace = create_function(
|
||||
'$m',
|
||||
'$r="\x00{$m[1]}ecursion_features";
|
||||
return \'s:\'.strlen($r.$m[2]).\':"\'.$r.$m[2].\'";\';'
|
||||
);
|
||||
|
||||
$replace = create_function(
|
||||
'$matches',
|
||||
'return $matches[1]?"-":"";'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class Drupal
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$replace = function ($m) use ($r) {
|
||||
$r = "\0{$m[1]}ecursion_features";
|
||||
return 's:' . strlen($r . $m[2]) . ':"' . $r . $m[2] . '";';
|
||||
};
|
||||
|
||||
$replace = function ($matches) {
|
||||
return $matches[1] ? "-" : "";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunction
|
||||
{
|
||||
public function run()
|
||||
|
@ -8,6 +10,22 @@ class ClassWithCreateFunction
|
|||
|
||||
$delimiter = '___';
|
||||
$callback = create_function('$matches', "return '$delimiter' . strtolower(\$matches[1]);");
|
||||
|
||||
$callback = create_function( '$caps', "return '$delimiter';" );
|
||||
|
||||
add_action(
|
||||
'widgets_init',
|
||||
create_function(
|
||||
'',
|
||||
'return register_widget( "WordAds_Sidebar_Widget" );'
|
||||
)
|
||||
);
|
||||
|
||||
$all_ids = array_map( create_function( '$o', 'return $o->ID;' ), $posts);
|
||||
|
||||
$missing = create_function( '$m', 'return $m->type === \'post\';');
|
||||
|
||||
create_function('$caps', "return '$caps';");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +33,8 @@ class ClassWithCreateFunction
|
|||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunction
|
||||
{
|
||||
public function run()
|
||||
|
@ -24,9 +44,32 @@ class ClassWithCreateFunction
|
|||
};
|
||||
|
||||
$delimiter = '___';
|
||||
$callback = function ($matches) use($delimiter) {
|
||||
$callback = function ($matches) use ($delimiter) {
|
||||
return $delimiter . strtolower($matches[1]);
|
||||
};
|
||||
|
||||
$callback = function ($caps) use ($delimiter) {
|
||||
return $delimiter;
|
||||
};
|
||||
|
||||
add_action(
|
||||
'widgets_init',
|
||||
function () {
|
||||
return register_widget("WordAds_Sidebar_Widget");
|
||||
}
|
||||
);
|
||||
|
||||
$all_ids = array_map( function ($o) {
|
||||
return $o->ID;
|
||||
}, $posts);
|
||||
|
||||
$missing = function ($m) {
|
||||
return $m->type === 'post';
|
||||
};
|
||||
|
||||
function ($caps) {
|
||||
return $caps;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class PhpNet
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
create_function('$x,$y', 'return "some trig: ".(sin($x) + $x*cos($y));');
|
||||
|
||||
create_function('$x,$y', 'return "a hypotenuse: ".sqrt($x*$x + $y*$y);');
|
||||
|
||||
create_function('$a,$b', 'if ($a >=0) {return "b*a^2 = ".$b*sqrt($a);} else {return false;}');
|
||||
|
||||
create_function('$a,$b', "return \"min(b^2+a, a^2,b) = \".min(\$a*\$a+\$b,\$b*\$b+\$a);");
|
||||
|
||||
create_function('$a,$b', 'if ($a > 0 && $b != 0) {return "ln(a)/b = ".log($a)/$b; } else { return false; }');
|
||||
|
||||
create_function('$b,$a', 'if (strncmp($a, $b, 3) == 0) return "** \"$a\" '.
|
||||
'and \"$b\"** Look the same to me! (looking at the first 3 chars)";');
|
||||
|
||||
create_function('$a,$b', '; return "CRCs: " . crc32($a) . ", ".crc32($b);');
|
||||
|
||||
create_function('$a,$b', '; return "similar(a,b) = " . similar_text($a, $b, &$p) . "($p%)";');
|
||||
|
||||
create_function('&$v,$k', '$v = $v . "mango";');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class PhpNet
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
function ($x, $y) {
|
||||
return "some trig: " . (sin($x) + $x * cos($y));
|
||||
};
|
||||
|
||||
function ($x, $y) {
|
||||
return "a hypotenuse: " . sqrt($x * $x + $y * $y);
|
||||
};
|
||||
|
||||
function ($a, $b) {
|
||||
if ($a >= 0) {
|
||||
return "b*a^2 = " . $b * sqrt($a);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function ($a, $b) {
|
||||
return "min(b^2+a, a^2,b) = " . min($a * $a + $b, $b * $b + $a);
|
||||
};
|
||||
|
||||
function ($a, $b) {
|
||||
if ($a > 0 && $b != 0) {
|
||||
return "ln(a)/b = " . log($a) / $b;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function ($b, $a) {
|
||||
if (strncmp($a, $b, 3) == 0) {
|
||||
return "** \"{$a}\" and \"{$b}\"** Look the same to me! (looking at the first 3 chars)";
|
||||
}
|
||||
};
|
||||
|
||||
function ($a, $b) {
|
||||
return "CRCs: " . crc32($a) . ", " . crc32($b);
|
||||
};
|
||||
|
||||
function ($a, $b) use ($p) {
|
||||
return "similar(a,b) = " . similar_text($a, $b, &$p) . "({$p}%)";
|
||||
};
|
||||
|
||||
function (&$v, $k) {
|
||||
$v = $v . "mango";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunctionWithReference
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$value = 1;
|
||||
$callback = create_function('&$attributes', $this->code);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunctionWithReference
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$value = 1;
|
||||
$callback = function (&$attributes) {
|
||||
eval($this->code);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunctionWithStackoverflow
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$func = create_function('$atts, $content = null','return "<div class=\"' . $class_list . '\">" . do_shortcode($content) . "</div>";' );
|
||||
|
||||
add_shortcode($shortcode, $func);
|
||||
|
||||
$this->translation_plural['callable'] = create_function('$n', $this->translation_plural['function']);
|
||||
|
||||
$newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Php\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ClassWithCreateFunctionWithStackoverflow
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$func = function ($atts, $content = null) use ($class_list) {
|
||||
return "<div class=\"{$class_list}\">" . do_shortcode($content) . "</div>";
|
||||
};
|
||||
|
||||
add_shortcode($shortcode, $func);
|
||||
|
||||
$this->translation_plural['callable'] = function ($n) {
|
||||
eval($this->translation_plural['function']);
|
||||
};
|
||||
|
||||
$newfunc = function ($a, $b) {
|
||||
return "ln({$a}) + ln({$b}) = " . log($a * $b);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -7,6 +7,7 @@ use PhpParser\Node;
|
|||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\BinaryOp\Coalesce;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
|
@ -58,6 +59,14 @@ final class BetterStandardPrinter extends Standard
|
|||
return '\'' . preg_replace("/'|\\\\(?=[\\\\']|$)/", '\\\\$0', $string) . '\'';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add space after "use ("
|
||||
*/
|
||||
protected function pExpr_Closure(Closure $node): string
|
||||
{
|
||||
return Strings::replace(parent::pExpr_Closure($node), '#( use)\(#', '$1 (');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not add "()" on Expressions
|
||||
* @see https://github.com/rectorphp/rector/pull/401#discussion_r181487199
|
||||
|
|
|
@ -93,7 +93,6 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
|
|||
*/
|
||||
public function afterTraverse(array $nodes): array
|
||||
{
|
||||
// @todo foreach, array autowire on Required in CommanderInterface[]
|
||||
if ($this->nodeAddingCommander->isActive()) {
|
||||
$nodes = $this->nodeAddingCommander->traverseNodes($nodes);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user