Skip to content

Commit eef2813

Browse files
authored
IBX-8805: Added Rector for deprecated twig functions & filters (#8)
For more details see https://issues.ibexa.co/browse/IBX-8805 and #8 Key changes: * Added Rector for deprecated twig functions & filters * Dropped dependency on twig
1 parent 4e3b809 commit eef2813

File tree

4 files changed

+296
-0
lines changed

4 files changed

+296
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Rector\Rule\Internal;
10+
11+
use PhpParser\Node;
12+
use PhpParser\Node\Expr\Array_;
13+
use PhpParser\Node\Expr\ArrayItem;
14+
use PhpParser\Node\Expr\New_;
15+
use PhpParser\Node\Stmt\ClassMethod;
16+
use Rector\Contract\Rector\ConfigurableRectorInterface;
17+
use Rector\Rector\AbstractRector;
18+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
19+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
20+
21+
/**
22+
* @internal This rule is internal, for Ibexa 1st party packages
23+
*/
24+
final class RemoveDeprecatedTwigDefinitionsRector extends AbstractRector implements ConfigurableRectorInterface
25+
{
26+
/** @var string|int|true */
27+
private $version;
28+
29+
/**
30+
* @throws \Exception
31+
*/
32+
public function getRuleDefinition(): RuleDefinition
33+
{
34+
return new RuleDefinition('Remove legacy eZ Systems & Ibexa class_alias calls', [new ConfiguredCodeSample(
35+
<<<'CODE_SAMPLE'
36+
public function getFunctions(): array
37+
{
38+
return [
39+
new TwigFunction(
40+
'old_function_name',
41+
null,
42+
[
43+
'is_safe' => ['html'],
44+
'needs_environment' => true,
45+
'deprecated' => '4.0',
46+
'alternative' => 'new_function_name',
47+
]
48+
),
49+
new TwigFunction(
50+
'new_function_name',
51+
null,
52+
[
53+
'is_safe' => ['html'],
54+
'needs_environment' => true,
55+
]
56+
),
57+
];
58+
}
59+
CODE_SAMPLE
60+
,
61+
<<<'CODE_SAMPLE'
62+
public function getFunctions(): array
63+
{
64+
return [
65+
new TwigFunction(
66+
'new_function_name',
67+
null,
68+
[
69+
'is_safe' => ['html'],
70+
'needs_environment' => true,
71+
]
72+
),
73+
];
74+
}
75+
CODE_SAMPLE
76+
,
77+
['var_dump']
78+
)]);
79+
}
80+
81+
public function getNodeTypes(): array
82+
{
83+
return [ClassMethod::class];
84+
}
85+
86+
public function refactor(Node $node): ?Node
87+
{
88+
// Ensure the method is named "getFunctions"
89+
if (!$node instanceof ClassMethod
90+
|| (
91+
!$this->isName($node, 'getFunctions')
92+
&& !$this->isName($node, 'getFilters')
93+
)
94+
) {
95+
return null;
96+
}
97+
98+
if ($node->stmts === null) {
99+
return null;
100+
}
101+
102+
// Look for the return statement within the method
103+
foreach ($node->stmts as $stmt) {
104+
if ($stmt instanceof Node\Stmt\Return_ && $stmt->expr instanceof Array_) {
105+
$arrayNode = $stmt->expr;
106+
107+
// Filter out TwigFunction declarations with the 'deprecated' option
108+
$arrayNode->items = array_filter($arrayNode->items, function (?ArrayItem $item) {
109+
if ($item === null || !$item->value instanceof New_) {
110+
return true;
111+
}
112+
113+
/** @var \PhpParser\Node\Expr\New_ $newExpr */
114+
$newExpr = $item->value;
115+
116+
// Ensure it's a 'Twig\TwigFunction' instantiation
117+
if (!$newExpr->class instanceof Node\Name
118+
|| (!$this->isName($newExpr->class, 'Twig\TwigFunction')
119+
&& !$this->isName($newExpr->class, 'Twig\TwigFilter'))
120+
) {
121+
return true;
122+
}
123+
124+
// Check if the third argument (options array) contains the 'deprecated' key
125+
if (isset($newExpr->args[2])
126+
&& $newExpr->args[2] instanceof Node\Arg
127+
&& $newExpr->args[2]->value instanceof Array_
128+
) {
129+
$optionsArray = $newExpr->args[2]->value;
130+
131+
foreach ($optionsArray->items as $optionItem) {
132+
if ($optionItem instanceof ArrayItem &&
133+
$optionItem->key instanceof Node\Scalar\String_ &&
134+
$optionItem->key->value === 'deprecated' &&
135+
$optionItem->value instanceof Node\Scalar\String_ &&
136+
$optionItem->value->value === $this->version
137+
) {
138+
// Skip this item if 'deprecated' is found
139+
return false;
140+
}
141+
}
142+
}
143+
144+
return true;
145+
});
146+
}
147+
}
148+
149+
return $node;
150+
}
151+
152+
public function configure(array $configuration): void
153+
{
154+
$this->version = $configuration['version'] ?? true;
155+
}
156+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php /** @noinspection ALL */
2+
3+
use Twig\TwigFilter;
4+
use Twig\TwigFunction;
5+
use Twig\Extension\AbstractExtension;
6+
7+
class SomeClassWithTwigFunctions extends AbstractExtension
8+
{
9+
public function getFunctions(): array
10+
{
11+
return [
12+
new TwigFunction(
13+
'old_function_name',
14+
null,
15+
[
16+
'is_safe' => ['html'],
17+
'needs_environment' => true,
18+
'deprecated' => '4.0',
19+
'alternative' => 'new_function_name',
20+
]
21+
),
22+
new TwigFunction(
23+
'new_function_name',
24+
null,
25+
[
26+
'is_safe' => ['html'],
27+
'needs_environment' => true,
28+
]
29+
),
30+
];
31+
}
32+
33+
public function getFilters(): array
34+
{
35+
return [
36+
new TwigFilter(
37+
'old_filter',
38+
[$this, 'someCallback'],
39+
['deprecated' => '4.0']
40+
),
41+
new TwigFilter(
42+
'new_filter',
43+
[$this, 'someCallback'],
44+
),
45+
];
46+
}
47+
}
48+
49+
?>
50+
-----
51+
<?php /** @noinspection ALL */
52+
53+
use Twig\TwigFilter;
54+
use Twig\TwigFunction;
55+
use Twig\Extension\AbstractExtension;
56+
57+
class SomeClassWithTwigFunctions extends AbstractExtension
58+
{
59+
public function getFunctions(): array
60+
{
61+
return [
62+
new TwigFunction(
63+
'new_function_name',
64+
null,
65+
[
66+
'is_safe' => ['html'],
67+
'needs_environment' => true,
68+
]
69+
),
70+
];
71+
}
72+
73+
public function getFilters(): array
74+
{
75+
return [
76+
new TwigFilter(
77+
'new_filter',
78+
[$this, 'someCallback'],
79+
),
80+
];
81+
}
82+
}
83+
84+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Rector\Tests\Rule\Internal\RemoveDeprecatedTwigDefinitionsRector;
10+
11+
use PHPUnit\Framework\Attributes\DataProvider;
12+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
13+
14+
/**
15+
* @covers \Ibexa\Rector\Rule\Internal\RemoveDeprecatedTwigDefinitionsRector
16+
*/
17+
final class RemoveDeprecatedTwigDefinitionsRectorTest extends AbstractRectorTestCase
18+
{
19+
/**
20+
* @throws \Rector\Exception\ShouldNotHappenException
21+
*/
22+
#[DataProvider('provideData')]
23+
public function test(string $filePath): void
24+
{
25+
$this->doTestFile($filePath);
26+
}
27+
28+
public static function provideData(): \Iterator
29+
{
30+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
31+
}
32+
33+
public function provideConfigFilePath(): string
34+
{
35+
return __DIR__ . '/config/configured_rule.php';
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
use Ibexa\Rector\Rule\Internal\RemoveDeprecatedTwigDefinitionsRector;
10+
use Rector\Config\RectorConfig;
11+
12+
return static function (RectorConfig $rectorConfig): void {
13+
$rectorConfig->ruleWithConfiguration(
14+
RemoveDeprecatedTwigDefinitionsRector::class,
15+
[
16+
'version' => '4.0',
17+
]
18+
);
19+
};

0 commit comments

Comments
 (0)