1414use PhpCsFixer \FixerDefinition \CodeSample ;
1515use PhpCsFixer \FixerDefinition \FixerDefinition ;
1616use PhpCsFixer \FixerDefinition \FixerDefinitionInterface ;
17+ use PhpCsFixer \Tokenizer \Analyzer \Analysis \NamespaceUseAnalysis ;
18+ use PhpCsFixer \Tokenizer \Analyzer \NamespaceUsesAnalyzer ;
1719use PhpCsFixer \Tokenizer \Token ;
1820use 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 ($ index , $ useDeclarations );
77+ if ($ name !== null ) {
78+ $ stringableInterfaces [] = $ name ;
79+ }
6880 continue ;
6981 }
7082
@@ -81,14 +93,36 @@ 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 (int $ index , array $ useDeclarations ): ?string
108+ {
109+ $ uses = \array_filter (
110+ $ useDeclarations ,
111+ static fn (NamespaceUseAnalysis $ namespaceUseAnalysis ): bool => $ namespaceUseAnalysis ->getStartIndex () === $ index ,
112+ );
113+
114+ \assert (\count ($ uses ) === 1 );
115+
116+ $ useDeclaration = \reset ($ uses );
117+
118+ $ lowercasedFullName = \strtolower ($ useDeclaration ->getFullName ());
119+ if ($ lowercasedFullName !== 'stringable ' && $ lowercasedFullName !== '\\stringable ' ) {
120+ return null ;
121+ }
122+
123+ return \strtolower ($ useDeclaration ->getShortName ());
124+ }
125+
92126 private static function doesHaveToStringMethod (Tokens $ tokens , int $ classStartIndex , int $ classEndIndex ): bool
93127 {
94128 $ index = $ classStartIndex ;
@@ -115,23 +149,25 @@ private static function doesHaveToStringMethod(Tokens $tokens, int $classStartIn
115149 return false ;
116150 }
117151
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 === []) {
152+ /**
153+ * @param list<string> $stringableInterfaces
154+ */
155+ private static function doesImplementStringable (
156+ Tokens $ tokens ,
157+ int $ classKeywordIndex ,
158+ int $ classOpenBraceIndex ,
159+ array $ stringableInterfaces
160+ ): bool {
161+ $ implementedInterfaces = self ::getInterfaces ($ tokens , $ classKeywordIndex , $ classOpenBraceIndex );
162+ if ($ implementedInterfaces === []) {
122163 return false ;
123164 }
124-
125- if (\in_array ('\\stringable ' , $ interfaces , true )) {
165+ if (\in_array ('\\stringable ' , $ implementedInterfaces , true )) {
126166 return true ;
127167 }
128168
129- if ($ namespaceStartIndex === 0 && \in_array ('stringable ' , $ interfaces , true )) {
130- return true ;
131- }
132-
133- foreach (self ::getImports ($ tokens , $ namespaceStartIndex , $ classKeywordIndex ) as $ import ) {
134- if (\in_array ($ import , $ interfaces , true )) {
169+ foreach ($ stringableInterfaces as $ stringableInterface ) {
170+ if (\in_array ($ stringableInterface , $ implementedInterfaces , true )) {
135171 return true ;
136172 }
137173 }
@@ -170,34 +206,6 @@ private static function getInterfaces(Tokens $tokens, int $classKeywordIndex, in
170206 return $ interfaces ;
171207 }
172208
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-
201209 private static function addStringableInterface (Tokens $ tokens , int $ classIndex ): void
202210 {
203211 $ implementsIndex = $ tokens ->getNextTokenOfKind ($ classIndex , ['{ ' , [\T_IMPLEMENTS ]]);
0 commit comments