@@ -55,7 +55,7 @@ public function isRisky(): bool
5555
5656 public function fix (\SplFileInfo $ file , Tokens $ tokens ): void
5757 {
58- $ namespaceStartIndex = null ;
58+ $ namespaceStartIndex = 0 ;
5959
6060 for ($ index = 1 ; $ index < $ tokens ->count (); $ index ++) {
6161 if ($ tokens [$ index ]->isGivenKind (\T_NAMESPACE )) {
@@ -76,7 +76,11 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void
7676 continue ;
7777 }
7878
79- $ this ->addStringableInterface ($ tokens , $ index , $ namespaceStartIndex );
79+ if ($ this ->doesImplementStringable ($ tokens , $ namespaceStartIndex , $ index + 1 , $ classStartIndex - 1 )) {
80+ continue ;
81+ }
82+
83+ $ this ->addStringableInterface ($ tokens , $ index );
8084 }
8185 }
8286
@@ -101,7 +105,92 @@ private function doesHaveToStringMethod(Tokens $tokens, int $classStartIndex, in
101105 return false ;
102106 }
103107
104- private function addStringableInterface (Tokens $ tokens , int $ classIndex , ?int $ namespaceStartIndex ): void
108+ private function doesImplementStringable (Tokens $ tokens , int $ namespaceStartIndex , int $ classKeywordIndex , int $ classOpenBraceIndex ): bool
109+ {
110+ $ interfaces = $ this ->getInterfaces ($ tokens , $ classKeywordIndex , $ classOpenBraceIndex );
111+ if ($ interfaces === []) {
112+ return false ;
113+ }
114+
115+ if (\in_array ('\stringable ' , $ interfaces , true )) {
116+ return true ;
117+ }
118+
119+ if ($ namespaceStartIndex === 0 && \in_array ('stringable ' , $ interfaces , true )) {
120+ return true ;
121+ }
122+
123+ foreach ($ this ->getImports ($ tokens , $ namespaceStartIndex , $ classKeywordIndex ) as $ import ) {
124+ if (\in_array ($ import , $ interfaces , true )) {
125+ return true ;
126+ }
127+ }
128+
129+ return false ;
130+ }
131+
132+ /**
133+ * @return array<string>
134+ */
135+ private function getInterfaces (Tokens $ tokens , int $ classKeywordIndex , int $ classOpenBraceIndex ): array
136+ {
137+ $ startIndex = $ tokens ->getNextTokenOfKind ($ classKeywordIndex , ['{ ' , [\T_IMPLEMENTS ]]);
138+ \assert (\is_int ($ startIndex ));
139+
140+ $ interfaces = [];
141+ for ($ index = $ startIndex ; $ index < $ classOpenBraceIndex ; $ index ++) {
142+ if (!$ tokens [$ index ]->isGivenKind (\T_STRING )) {
143+ continue ;
144+ }
145+
146+ $ interface = \strtolower ($ tokens [$ index ]->getContent ());
147+
148+ $ prevIndex = $ tokens ->getPrevMeaningfulToken ($ index );
149+ \assert (\is_int ($ prevIndex ));
150+ if ($ tokens [$ prevIndex ]->isGivenKind (\T_NS_SEPARATOR )) {
151+ $ interface = '\\' . $ interface ;
152+ $ prevPrevIndex = $ tokens ->getPrevMeaningfulToken ($ prevIndex );
153+ \assert (\is_int ($ prevPrevIndex ));
154+ if ($ tokens [$ prevPrevIndex ]->isGivenKind (\T_STRING )) {
155+ continue ;
156+ }
157+ }
158+
159+ $ interfaces [] = $ interface ;
160+ }
161+
162+ return $ interfaces ;
163+ }
164+
165+ /**
166+ * @return iterable<string>
167+ */
168+ private function getImports (Tokens $ tokens , int $ namespaceStartIndex , int $ classKeywordIndex ): iterable
169+ {
170+ for ($ index = $ namespaceStartIndex ; $ index < $ classKeywordIndex ; $ index ++) {
171+ if (!$ tokens [$ index ]->isGivenKind (\T_USE )) {
172+ continue ;
173+ }
174+ $ nameIndex = $ tokens ->getNextMeaningfulToken ($ index );
175+ \assert (\is_int ($ nameIndex ));
176+
177+ if ($ tokens [$ nameIndex ]->isGivenKind (\T_NS_SEPARATOR )) {
178+ $ nameIndex = $ tokens ->getNextMeaningfulToken ($ nameIndex );
179+ \assert (\is_int ($ nameIndex ));
180+ }
181+
182+ $ nextIndex = $ tokens ->getNextMeaningfulToken ($ nameIndex );
183+ \assert (\is_int ($ nextIndex ));
184+ if ($ tokens [$ nextIndex ]->isGivenKind (\T_AS )) {
185+ $ nameIndex = $ tokens ->getNextMeaningfulToken ($ nextIndex );
186+ \assert (\is_int ($ nameIndex ));
187+ }
188+
189+ yield \strtolower ($ tokens [$ nameIndex ]->getContent ());
190+ }
191+ }
192+
193+ private function addStringableInterface (Tokens $ tokens , int $ classIndex ): void
105194 {
106195 $ implementsIndex = $ tokens ->getNextTokenOfKind ($ classIndex , ['{ ' , [\T_IMPLEMENTS ]]);
107196 \assert (\is_int ($ implementsIndex ));
@@ -126,9 +215,6 @@ private function addStringableInterface(Tokens $tokens, int $classIndex, ?int $n
126215
127216 $ implementsEndIndex = $ tokens ->getNextTokenOfKind ($ implementsIndex , ['{ ' ]);
128217 \assert (\is_int ($ implementsEndIndex ));
129- if ($ this ->isStringableAlreadyUsed ($ tokens , $ implementsIndex + 1 , $ implementsEndIndex - 1 , $ namespaceStartIndex )) {
130- return ;
131- }
132218
133219 $ prevIndex = $ tokens ->getPrevMeaningfulToken ($ implementsEndIndex );
134220 \assert (\is_int ($ prevIndex ));
@@ -143,57 +229,4 @@ private function addStringableInterface(Tokens $tokens, int $classIndex, ?int $n
143229 ]
144230 );
145231 }
146-
147- private function isStringableAlreadyUsed (Tokens $ tokens , int $ implementsStartIndex , int $ implementsEndIndex , ?int $ namespaceStartIndex ): bool
148- {
149- for ($ index = $ implementsStartIndex ; $ index < $ implementsEndIndex ; $ index ++) {
150- if (!$ tokens [$ index ]->equals ([\T_STRING , 'Stringable ' ], false )) {
151- continue ;
152- }
153-
154- $ namespaceSeparatorIndex = $ tokens ->getPrevMeaningfulToken ($ index );
155- \assert (\is_int ($ namespaceSeparatorIndex ));
156-
157- if ($ tokens [$ namespaceSeparatorIndex ]->isGivenKind (\T_NS_SEPARATOR )) {
158- $ beforeNamespaceSeparatorIndex = $ tokens ->getPrevMeaningfulToken ($ namespaceSeparatorIndex );
159- \assert (\is_int ($ beforeNamespaceSeparatorIndex ));
160-
161- if (!$ tokens [$ beforeNamespaceSeparatorIndex ]->isGivenKind (\T_STRING )) {
162- return true ;
163- }
164- } else {
165- if ($ namespaceStartIndex === null ) {
166- return true ;
167- }
168- if ($ this ->isStringableImported ($ tokens , $ namespaceStartIndex , $ index )) {
169- return true ;
170- }
171- }
172- }
173-
174- return false ;
175- }
176-
177- private function isStringableImported (Tokens $ tokens , int $ startIndex , int $ endIndex ): bool
178- {
179- for ($ index = $ startIndex ; $ index < $ endIndex ; $ index ++) {
180- if (!$ tokens [$ index ]->equals ([\T_STRING , 'Stringable ' ], false )) {
181- continue ;
182- }
183-
184- $ useIndex = $ tokens ->getPrevMeaningfulToken ($ index );
185- \assert (\is_int ($ useIndex ));
186-
187- if ($ tokens [$ useIndex ]->isGivenKind (\T_NS_SEPARATOR )) {
188- $ useIndex = $ tokens ->getPrevMeaningfulToken ($ useIndex );
189- \assert (\is_int ($ useIndex ));
190- }
191-
192- if ($ tokens [$ useIndex ]->isGivenKind (\T_USE )) {
193- return true ;
194- }
195- }
196-
197- return false ;
198- }
199232}
0 commit comments