Skip to content

Commit 932a8f7

Browse files
committed
wip rector
1 parent 5265fe4 commit 932a8f7

4 files changed

Lines changed: 201 additions & 28 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
/.github/workflows/benchmark.yml @xHeaven
1515
/.github/workflows/benchmark-comment.yml @xHeaven
1616
/src/ @brendt
17+
/utils/rector/ @xHeaven
1718
/docs/ @brendt @innocenzi
1819
/packages/auth/ @innocenzi
1920
/packages/cache/ @innocenzi

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@
237237
"Tempest\\Validation\\Tests\\": "packages/validation/tests",
238238
"Tempest\\View\\Tests\\": "packages/view/tests",
239239
"Tempest\\Vite\\Tests\\": "packages/vite/tests",
240-
"Tests\\Tempest\\": "tests"
240+
"Tests\\Tempest\\": "tests",
241+
"Tempest\\Rector\\": "utils/rector/src"
241242
}
242243
},
243244
"bin": [

rector.php

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,71 +2,69 @@
22

33
declare(strict_types=1);
44

5-
use Rector\Arguments\Rector\ClassMethod\ArgumentAdderRector;
65
use Rector\Caching\ValueObject\Storage\FileCacheStorage;
6+
use Rector\CodeQuality\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector;
7+
use Rector\CodingStyle\Rector\Catch_\CatchExceptionNameMatchingTypeRector;
78
use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector;
89
use Rector\Config\RectorConfig;
9-
use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector;
10+
use Rector\EarlyReturn\Rector\Return_\ReturnBinaryOrToEarlyReturnRector;
11+
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
1012
use Rector\Php70\Rector\StaticCall\StaticCallOnNonStaticToInstanceCallRector;
11-
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
1213
use Rector\Php74\Rector\Property\RestoreDefaultNullToNullableTypePropertyRector;
1314
use Rector\Php81\Rector\Array_\ArrayToFirstClassCallableRector;
1415
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
1516
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
16-
use Rector\Php82\Rector\Class_\ReadOnlyClassRector;
1717
use Rector\Php82\Rector\Param\AddSensitiveParameterAttributeRector;
1818
use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;
19-
use Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector;
19+
use Rector\Php85\Rector\FuncCall\ArrayKeyExistsNullToEmptyStringRector;
2020
use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector;
21-
use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector;
22-
use Rector\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector;
23-
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
24-
use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector;
21+
use Tempest\Rector\ImportClassNamesRector;
2522

2623
return RectorConfig::configure()
2724
->withCache('./.cache/rector', FileCacheStorage::class)
2825
->withPaths([
2926
__DIR__ . '/src',
3027
__DIR__ . '/tests',
28+
__DIR__ . '/packages',
3129
])
32-
->withSkipPath(__DIR__ . '/tests/PHPStan/QueryFunctionDynamicReturnTypeExtension.php')
3330
->withConfiguredRule(AddSensitiveParameterAttributeRector::class, [
3431
'sensitive_parameters' => [
3532
'password',
3633
'secret',
3734
],
3835
])
39-
->withRules([
40-
ExplicitNullableParamTypeRector::class,
41-
])
4236
->withSkip([
37+
'*.stub.php',
38+
'*.input.php',
39+
'*.expected.php',
40+
'*/Fixtures/*',
41+
__DIR__ . '/packages/intl/bin/plural-rules.php',
4342
AddOverrideAttributeToOverriddenMethodsRector::class,
44-
ArgumentAdderRector::class,
45-
ClosureToArrowFunctionRector::class,
46-
EmptyOnNullableObjectToInstanceOfRector::class,
4743
ArrayToFirstClassCallableRector::class,
4844
NullToStrictStringFuncCallArgRector::class,
49-
ReadOnlyClassRector::class,
5045
ReadOnlyPropertyRector::class,
51-
RemoveNullPropertyInitializationRector::class,
5246
AddSensitiveParameterAttributeRector::class,
5347
RestoreDefaultNullToNullableTypePropertyRector::class,
54-
ReturnNeverTypeRector::class,
5548
StaticCallOnNonStaticToInstanceCallRector::class,
5649
EncapsedStringsToSprintfRector::class,
57-
AddArrowFunctionReturnTypeRector::class,
5850
PrivatizeFinalClassMethodRector::class,
59-
NarrowObjectReturnTypeRector::class,
51+
IssetOnPropertyObjectToPropertyExistsRector::class,
52+
CatchExceptionNameMatchingTypeRector::class,
53+
ArrayKeyExistsNullToEmptyStringRector::class,
54+
StringClassNameToClassConstantRector::class,
55+
ReturnBinaryOrToEarlyReturnRector::class,
56+
])
57+
->withConfiguredRule(ImportClassNamesRector::class, [
58+
'importShortClasses' => true,
59+
'excludedClasses' => [
60+
'\Redis',
61+
],
6062
])
61-
->withParallel(300, 10, 10)
6263
->withPreparedSets(
63-
codeQuality: false,
64+
codeQuality: true,
6465
codingStyle: true,
6566
privatization: true,
6667
naming: false,
6768
earlyReturn: true,
6869
)
69-
->withDeadCodeLevel(40)
70-
->withMemoryLimit('3G')
71-
->withPhpSets(php83: true)
72-
->withTypeCoverageLevel(37);
70+
->withPhpSets(php85: true);
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Rector;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Name;
9+
use PhpParser\Node\Name\FullyQualified;
10+
use PhpParser\Node\Stmt\GroupUse;
11+
use PhpParser\Node\Stmt\Use_;
12+
use Rector\CodingStyle\Node\NameImporter;
13+
use Rector\Configuration\Option;
14+
use Rector\Configuration\Parameter\SimpleParameterProvider;
15+
use Rector\Contract\Rector\ConfigurableRectorInterface;
16+
use Rector\Naming\Naming\UseImportsResolver;
17+
use Rector\NodeTypeResolver\Node\AttributeKey;
18+
use Rector\Rector\AbstractRector;
19+
20+
final class ImportClassNamesRector extends AbstractRector implements ConfigurableRectorInterface
21+
{
22+
private ?string $currentNamespace = null;
23+
24+
private ?string $currentFilePath = null;
25+
26+
/** @var list<string> */
27+
private array $excludedClasses = [];
28+
29+
public function __construct(
30+
private readonly NameImporter $nameImporter,
31+
private readonly UseImportsResolver $useImportsResolver,
32+
) {
33+
}
34+
35+
/**
36+
* @param array{importShortClasses?: bool, excludedClasses?: list<string>} $configuration
37+
*/
38+
public function configure(array $configuration): void
39+
{
40+
SimpleParameterProvider::setParameter(
41+
Option::IMPORT_SHORT_CLASSES,
42+
$configuration['importShortClasses'] ?? true,
43+
);
44+
45+
$this->excludedClasses = array_map(
46+
static fn (string $class): string => ltrim($class, '\\'),
47+
$configuration['excludedClasses'] ?? [],
48+
);
49+
}
50+
51+
public function getNodeTypes(): array
52+
{
53+
return [FullyQualified::class];
54+
}
55+
56+
public function refactor(Node $node): ?Name
57+
{
58+
if (! $node instanceof FullyQualified) {
59+
return null;
60+
}
61+
62+
if ($node->getAttribute(AttributeKey::IS_FUNCCALL_NAME) === true) {
63+
return null;
64+
}
65+
66+
if ($node->getAttribute(AttributeKey::IS_CONSTFETCH_NAME) === true) {
67+
return null;
68+
}
69+
70+
if ($this->isAlreadyShortInSource($node)) {
71+
return null;
72+
}
73+
74+
if (in_array($node->toString(), $this->excludedClasses, true)) {
75+
return null;
76+
}
77+
78+
$currentUses = $this->useImportsResolver->resolve();
79+
80+
$imported = $this->nameImporter->importName($node, $this->file, $currentUses);
81+
82+
if ($imported instanceof Name) {
83+
return $imported;
84+
}
85+
86+
return $this->shortenSameNamespaceName($node, $currentUses);
87+
}
88+
89+
/**
90+
* @param array<Use_|GroupUse> $currentUses
91+
*/
92+
private function shortenSameNamespaceName(FullyQualified $node, array $currentUses): ?Name
93+
{
94+
$namespaceName = $this->resolveCurrentNamespace();
95+
96+
if ($namespaceName === null) {
97+
return null;
98+
}
99+
100+
$fqcn = $node->toString();
101+
$shortName = $node->getLast();
102+
$fqcnNamespace = substr($fqcn, 0, -(strlen($shortName) + 1));
103+
104+
if ($fqcnNamespace !== $namespaceName) {
105+
return null;
106+
}
107+
108+
if ($this->hasConflictingUseStatement($shortName, $fqcn, $currentUses)) {
109+
return null;
110+
}
111+
112+
return new Name($shortName);
113+
}
114+
115+
private function isAlreadyShortInSource(FullyQualified $node): bool
116+
{
117+
$startTokenPos = $node->getStartTokenPos();
118+
$oldTokens = $this->file->getOldTokens();
119+
if (! isset($oldTokens[$startTokenPos])) {
120+
return false;
121+
}
122+
$originalText = $oldTokens[$startTokenPos]->text;
123+
return ! str_contains($originalText, '\\');
124+
}
125+
126+
private function resolveCurrentNamespace(): ?string
127+
{
128+
$filePath = $this->file->getFilePath();
129+
130+
if ($this->currentFilePath === $filePath) {
131+
return $this->currentNamespace;
132+
}
133+
134+
$this->currentFilePath = $filePath;
135+
$this->currentNamespace = null;
136+
137+
$fileNode = $this->file->getFileNode();
138+
139+
if ($fileNode === null) {
140+
return null;
141+
}
142+
143+
$namespace = $fileNode->getNamespace();
144+
145+
if ($namespace?->name !== null) {
146+
$this->currentNamespace = $namespace->name->toString();
147+
}
148+
149+
return $this->currentNamespace;
150+
}
151+
152+
/**
153+
* @param array<Use_|GroupUse> $currentUses
154+
*/
155+
private function hasConflictingUseStatement(string $shortName, string $fqcn, array $currentUses): bool
156+
{
157+
foreach ($currentUses as $use) {
158+
foreach ($use->uses as $useUse) {
159+
$importedName = $use instanceof GroupUse
160+
? $use->prefix . '\\' . $useUse->name->toString()
161+
: $useUse->name->toString();
162+
163+
$alias = $useUse->alias?->toString() ?? $useUse->name->getLast();
164+
165+
if ($alias === $shortName && $importedName !== $fqcn) {
166+
return true;
167+
}
168+
}
169+
}
170+
171+
return false;
172+
}
173+
}

0 commit comments

Comments
 (0)