Skip to content

Commit db3b752

Browse files
committed
StringableInterfaceFixer - fixer for not Stringable import with alias
1 parent 1623c49 commit db3b752

File tree

3 files changed

+68
-44
lines changed

3 files changed

+68
-44
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
66
[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net)
77
[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE)
8-
![Tests](https://img.shields.io/badge/tests-3798-brightgreen.svg)
8+
![Tests](https://img.shields.io/badge/tests-3799-brightgreen.svg)
99
[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
1010

1111
[![CI status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml/badge.svg)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions/workflows/ci.yaml)

src/Fixer/StringableInterfaceFixer.php

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use PhpCsFixer\FixerDefinition\CodeSample;
1515
use PhpCsFixer\FixerDefinition\FixerDefinition;
1616
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
17+
use PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis;
18+
use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer;
1719
use PhpCsFixer\Tokenizer\Token;
1820
use PhpCsFixer\Tokenizer\Tokens;
1921

@@ -60,11 +62,21 @@ public function isRisky(): bool
6062

6163
public function fix(\SplFileInfo $file, Tokens $tokens): void
6264
{
63-
$namespaceStartIndex = 0;
65+
$useDeclarations = (new NamespaceUsesAnalyzer())->getDeclarationsFromTokens($tokens);
66+
67+
$stringableInterfaces = ['stringable'];
6468

6569
for ($index = 1; $index < $tokens->count(); $index++) {
6670
if ($tokens[$index]->isGivenKind(\T_NAMESPACE)) {
67-
$namespaceStartIndex = $index;
71+
$stringableInterfaces = [];
72+
continue;
73+
}
74+
75+
if ($tokens[$index]->isGivenKind(\T_USE)) {
76+
$name = self::getNameFromUse($tokens, $index, $useDeclarations);
77+
if ($name !== null) {
78+
$stringableInterfaces[] = $name;
79+
}
6880
continue;
6981
}
7082

@@ -81,14 +93,35 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void
8193
continue;
8294
}
8395

84-
if (self::doesImplementStringable($tokens, $namespaceStartIndex, $index, $classStartIndex)) {
96+
if (self::doesImplementStringable($tokens, $index, $classStartIndex, $stringableInterfaces)) {
8597
continue;
8698
}
8799

88100
self::addStringableInterface($tokens, $index);
89101
}
90102
}
91103

104+
/**
105+
* @param list<NamespaceUseAnalysis> $useDeclarations
106+
*/
107+
private static function getNameFromUse(Tokens $tokens, int $index, array $useDeclarations): ?string
108+
{
109+
foreach ($useDeclarations as $useDeclaration) {
110+
if ($useDeclaration->getStartIndex() !== $index) {
111+
continue;
112+
}
113+
114+
$lowercasedFullName = \strtolower($useDeclaration->getFullName());
115+
if ($lowercasedFullName !== 'stringable' && $lowercasedFullName !== '\\stringable') {
116+
return null;
117+
}
118+
119+
return \strtolower($useDeclaration->getShortName());
120+
}
121+
122+
return null;
123+
}
124+
92125
private static function doesHaveToStringMethod(Tokens $tokens, int $classStartIndex, int $classEndIndex): bool
93126
{
94127
$index = $classStartIndex;
@@ -115,23 +148,25 @@ private static function doesHaveToStringMethod(Tokens $tokens, int $classStartIn
115148
return false;
116149
}
117150

118-
private static function doesImplementStringable(Tokens $tokens, int $namespaceStartIndex, int $classKeywordIndex, int $classOpenBraceIndex): bool
119-
{
120-
$interfaces = self::getInterfaces($tokens, $classKeywordIndex, $classOpenBraceIndex);
121-
if ($interfaces === []) {
151+
/**
152+
* @param list<string> $stringableInterfaces
153+
*/
154+
private static function doesImplementStringable(
155+
Tokens $tokens,
156+
int $classKeywordIndex,
157+
int $classOpenBraceIndex,
158+
array $stringableInterfaces
159+
): bool {
160+
$implementedInterfaces = self::getInterfaces($tokens, $classKeywordIndex, $classOpenBraceIndex);
161+
if ($implementedInterfaces === []) {
122162
return false;
123163
}
124-
125-
if (\in_array('\\stringable', $interfaces, true)) {
126-
return true;
127-
}
128-
129-
if ($namespaceStartIndex === 0 && \in_array('stringable', $interfaces, true)) {
164+
if (\in_array('\\stringable', $implementedInterfaces, true)) {
130165
return true;
131166
}
132167

133-
foreach (self::getImports($tokens, $namespaceStartIndex, $classKeywordIndex) as $import) {
134-
if (\in_array($import, $interfaces, true)) {
168+
foreach ($stringableInterfaces as $stringableInterface) {
169+
if (\in_array($stringableInterface, $implementedInterfaces, true)) {
135170
return true;
136171
}
137172
}
@@ -170,34 +205,6 @@ private static function getInterfaces(Tokens $tokens, int $classKeywordIndex, in
170205
return $interfaces;
171206
}
172207

173-
/**
174-
* @return iterable<string>
175-
*/
176-
private static function getImports(Tokens $tokens, int $namespaceStartIndex, int $classKeywordIndex): iterable
177-
{
178-
for ($index = $namespaceStartIndex; $index < $classKeywordIndex; $index++) {
179-
if (!$tokens[$index]->isGivenKind(\T_USE)) {
180-
continue;
181-
}
182-
$nameIndex = $tokens->getNextMeaningfulToken($index);
183-
\assert(\is_int($nameIndex));
184-
185-
if ($tokens[$nameIndex]->isGivenKind(\T_NS_SEPARATOR)) {
186-
$nameIndex = $tokens->getNextMeaningfulToken($nameIndex);
187-
\assert(\is_int($nameIndex));
188-
}
189-
190-
$nextIndex = $tokens->getNextMeaningfulToken($nameIndex);
191-
\assert(\is_int($nextIndex));
192-
if ($tokens[$nextIndex]->isGivenKind(\T_AS)) {
193-
$nameIndex = $tokens->getNextMeaningfulToken($nextIndex);
194-
\assert(\is_int($nameIndex));
195-
}
196-
197-
yield \strtolower($tokens[$nameIndex]->getContent());
198-
}
199-
}
200-
201208
private static function addStringableInterface(Tokens $tokens, int $classIndex): void
202209
{
203210
$implementsIndex = $tokens->getNextTokenOfKind($classIndex, ['{', [\T_IMPLEMENTS]]);

tests/Fixer/StringableInterfaceFixerTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,23 @@ public function __toString() { return "Foo"; }
152152
}
153153
';
154154

155+
yield [
156+
<<<'PHP'
157+
<?php
158+
use NotStringable as Stringy;
159+
class Bar implements \Stringable, Stringy {
160+
public function __toString() { return ""; }
161+
}
162+
PHP,
163+
<<<'PHP'
164+
<?php
165+
use NotStringable as Stringy;
166+
class Bar implements Stringy {
167+
public function __toString() { return ""; }
168+
}
169+
PHP,
170+
];
171+
155172
$implementedInterfacesCases = [
156173
\Stringable::class,
157174
'Foo\\Stringable',

0 commit comments

Comments
 (0)