Skip to content

Instantly share code, notes, and snippets.

@mstrelan
Created November 9, 2023 03:22
Show Gist options
  • Save mstrelan/4ebedc887ba0b7ea6d7b27784e316216 to your computer and use it in GitHub Desktop.
Save mstrelan/4ebedc887ba0b7ea6d7b27784e316216 to your computer and use it in GitHub Desktop.
FormattableMarkupToSprintfRector
<?php
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\NodeFactory\NodeFactory;
use Rector\Core\Rector\AbstractRector;
use Rector\Drupal\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class FormattableMarkupToSprintfRector extends AbstractRector implements RectorInterface
{
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Replace FormattableMarkup with sprintf', [new CodeSample(<<<'CODE_SAMPLE'
function testSomething()
{
$this->assertSame($expected, $actual, new FormattableMarkup('Expected @expected but found @actual.', ['@expected' => $expected, '@actual' => $actual]));
}
CODE_SAMPLE
, <<<'CODE_SAMPLE'
function testSomething()
{
$this->assertSame($expected, $actual, sprintf('Expected %s but found %s.', $expected, $actual));
}
CODE_SAMPLE
)]);
}
public function getNodeTypes(): array
{
return [MethodCall::class];
}
public function refactor(Node $node): ?Node
{
/** @var \PhpParser\Node\Expr\MethodCall $node */
$methodCallName = $this->getName($node->name);
if ($methodCallName === null) {
return null;
}
// we only care about "assert*" method names
if (!str_starts_with($methodCallName, 'assert')) {
return NULL;
}
foreach ($node->args as $index => $argument) {
if (!$argument->value instanceof Node\Expr\New_) {
continue;
}
if ($argument->value->class->toCodeString() === '\Drupal\Component\Render\FormattableMarkup') {
$this->replaceFormattableMarkupArgumentWithSprintf($node, $index);
}
}
return $node;
}
// @todo handle cases where replacements are in different order or appear multiple times.
private function replaceFormattableMarkupArgumentWithSprintf(MethodCall $node, int $argumentIndex): void
{
/** @var \PhpParser\Node\Expr\New_ $formattableMarkupArgument */
$formattableMarkupArgument = $node->args[$argumentIndex]->value;
$args = $formattableMarkupArgument->getArgs();
$formatString = $args[0]->value->value;
$replacementArgs = $args[1]->value->items;
$placeholders = [];
$replacements = [];
foreach ($replacementArgs as $replacementArg) {
$placeholders[] = $replacementArg->key->value;
$replacements[] = $replacementArg->value;
}
$formatString = str_replace($placeholders, '%s', $formatString);
// Replace the FormattableMarkup argument with the sprintf call
$node->args[$argumentIndex]->value = new FuncCall(
new Name('sprintf'),
[
new Arg(new String_($formatString)),
... array_map(fn ($value) => new Arg($value), $replacements),
]
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment