44
55namespace Rector \TypeDeclaration \Rector \ClassMethod ;
66
7- use Nette \Utils \Strings ;
87use PhpParser \Node ;
9- use PhpParser \Node \ArrayItem ;
10- use PhpParser \Node \Attribute ;
11- use PhpParser \Node \AttributeGroup ;
12- use PhpParser \Node \Expr \Array_ ;
13- use PhpParser \Node \Expr \Yield_ ;
14- use PhpParser \Node \Scalar \String_ ;
158use PhpParser \Node \Stmt \Class_ ;
169use PhpParser \Node \Stmt \ClassMethod ;
17- use PhpParser \Node \Stmt \Return_ ;
18- use PHPStan \PhpDocParser \Ast \PhpDoc \GenericTagValueNode ;
19- use PHPStan \PhpDocParser \Ast \PhpDoc \PhpDocTagNode ;
20- use PHPStan \Type \Constant \ConstantArrayType ;
2110use PHPStan \Type \MixedType ;
22- use PHPStan \Type \Type ;
23- use PHPStan \Type \TypeCombinator ;
24- use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfo ;
25- use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfoFactory ;
26- use Rector \NodeTypeResolver \PHPStan \Type \TypeFactory ;
27- use Rector \PhpParser \Node \BetterNodeFinder ;
2811use Rector \PHPStanStaticTypeMapper \Enum \TypeKind ;
2912use Rector \PHPUnit \NodeAnalyzer \TestsNodeAnalyzer ;
3013use Rector \Rector \AbstractRector ;
3114use Rector \StaticTypeMapper \StaticTypeMapper ;
15+ use Rector \TypeDeclaration \TypeAnalyzer \ParameterTypeFromDataProviderResolver ;
3216use Rector \TypeDeclaration \ValueObject \DataProviderNodes ;
17+ use Rector \TypeDeclarationDocblocks \NodeFinder \DataProviderMethodsFinder ;
3318use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
3419use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
3520
@@ -43,17 +28,10 @@ final class AddParamTypeBasedOnPHPUnitDataProviderRector extends AbstractRector
4328 */
4429 private const ERROR_MESSAGE = 'Adds param type declaration based on PHPUnit provider return type declaration ' ;
4530
46- /**
47- * @see https://regex101.com/r/hW09Vt/1
48- * @var string
49- */
50- private const METHOD_NAME_REGEX = '#^(?<method_name>\w+)(\(\))?# ' ;
51-
5231 public function __construct (
53- private readonly TypeFactory $ typeFactory ,
5432 private readonly TestsNodeAnalyzer $ testsNodeAnalyzer ,
55- private readonly PhpDocInfoFactory $ phpDocInfoFactory ,
56- private readonly BetterNodeFinder $ betterNodeFinder ,
33+ private readonly DataProviderMethodsFinder $ dataProviderMethodsFinder ,
34+ private readonly ParameterTypeFromDataProviderResolver $ parameterTypeFromDataProviderResolver ,
5735 private readonly StaticTypeMapper $ staticTypeMapper ,
5836 ) {
5937 }
@@ -131,12 +109,12 @@ public function refactor(Node $node): ?Node
131109 continue ;
132110 }
133111
134- $ dataProviderNodes = $ this ->resolveDataProviderNodes ( $ classMethod );
135- if ($ dataProviderNodes ->isEmpty () ) {
112+ $ dataProviderNodes = $ this ->dataProviderMethodsFinder -> findDataProviderNodes ( $ node , $ classMethod );
113+ if ($ dataProviderNodes ->getClassMethods () === [] ) {
136114 continue ;
137115 }
138116
139- $ hasClassMethodChanged = $ this ->refactorClassMethod ($ classMethod , $ node , $ dataProviderNodes-> nodes );
117+ $ hasClassMethodChanged = $ this ->refactorClassMethod ($ classMethod , $ dataProviderNodes );
140118 if ($ hasClassMethodChanged ) {
141119 $ hasChanged = true ;
142120 }
@@ -149,187 +127,7 @@ public function refactor(Node $node): ?Node
149127 return null ;
150128 }
151129
152- private function inferParam (
153- Class_ $ class ,
154- int $ parameterPosition ,
155- PhpDocTagNode | Attribute $ dataProviderNode
156- ): Type {
157- $ dataProviderClassMethod = $ this ->resolveDataProviderClassMethod ($ class , $ dataProviderNode );
158- if (! $ dataProviderClassMethod instanceof ClassMethod) {
159- return new MixedType ();
160- }
161-
162- $ returns = $ this ->betterNodeFinder ->findReturnsScoped ($ dataProviderClassMethod );
163- if ($ returns !== []) {
164- return $ this ->resolveReturnStaticArrayTypeByParameterPosition ($ returns , $ parameterPosition );
165- }
166-
167- /** @var Yield_[] $yields */
168- $ yields = $ this ->betterNodeFinder ->findInstancesOfInFunctionLikeScoped ($ dataProviderClassMethod , Yield_::class);
169- return $ this ->resolveYieldStaticArrayTypeByParameterPosition ($ yields , $ parameterPosition );
170- }
171-
172- private function resolveDataProviderClassMethod (
173- Class_ $ class ,
174- Attribute | PhpDocTagNode $ dataProviderNode
175- ): ?ClassMethod {
176- if ($ dataProviderNode instanceof Attribute) {
177- $ value = $ dataProviderNode ->args [0 ]->value ;
178-
179- if (! $ value instanceof String_) {
180- return null ;
181- }
182-
183- $ content = $ value ->value ;
184- } elseif ($ dataProviderNode ->value instanceof GenericTagValueNode) {
185- $ content = $ dataProviderNode ->value ->value ;
186- } else {
187- return null ;
188- }
189-
190- $ match = Strings::match ($ content , self ::METHOD_NAME_REGEX );
191- if ($ match === null ) {
192- return null ;
193- }
194-
195- $ methodName = $ match ['method_name ' ];
196- return $ class ->getMethod ($ methodName );
197- }
198-
199- /**
200- * @param Return_[] $returns
201- */
202- private function resolveReturnStaticArrayTypeByParameterPosition (array $ returns , int $ parameterPosition ): Type
203- {
204- $ firstReturnedExpr = $ returns [0 ]->expr ;
205-
206- if (! $ firstReturnedExpr instanceof Array_) {
207- return new MixedType ();
208- }
209-
210- $ paramOnPositionTypes = $ this ->resolveParamOnPositionTypes ($ firstReturnedExpr , $ parameterPosition );
211- if ($ paramOnPositionTypes === []) {
212- return new MixedType ();
213- }
214-
215- return $ this ->typeFactory ->createMixedPassedOrUnionType ($ paramOnPositionTypes );
216- }
217-
218- /**
219- * @param Yield_[] $yields
220- */
221- private function resolveYieldStaticArrayTypeByParameterPosition (array $ yields , int $ parameterPosition ): Type
222- {
223- $ paramOnPositionTypes = [];
224-
225- foreach ($ yields as $ yield ) {
226- if (! $ yield ->value instanceof Array_) {
227- continue ;
228- }
229-
230- $ type = $ this ->getTypeFromClassMethodYield ($ yield ->value );
231-
232- if (! $ type instanceof ConstantArrayType) {
233- return $ type ;
234- }
235-
236- foreach ($ type ->getValueTypes () as $ position => $ valueType ) {
237- if ($ position !== $ parameterPosition ) {
238- continue ;
239- }
240-
241- $ paramOnPositionTypes [] = $ valueType ;
242- }
243- }
244-
245- if ($ paramOnPositionTypes === []) {
246- return new MixedType ();
247- }
248-
249- return $ this ->typeFactory ->createMixedPassedOrUnionType ($ paramOnPositionTypes );
250- }
251-
252- private function getTypeFromClassMethodYield (Array_ $ classMethodYieldArray ): MixedType | ConstantArrayType
253- {
254- $ arrayType = $ this ->nodeTypeResolver ->getType ($ classMethodYieldArray );
255-
256- // impossible to resolve
257- if (! $ arrayType instanceof ConstantArrayType) {
258- return new MixedType ();
259- }
260-
261- return $ arrayType ;
262- }
263-
264- /**
265- * @return Type[]
266- */
267- private function resolveParamOnPositionTypes (Array_ $ array , int $ parameterPosition ): array
268- {
269- $ paramOnPositionTypes = [];
270-
271- foreach ($ array ->items as $ singleDataProvidedSet ) {
272- if (! $ singleDataProvidedSet instanceof ArrayItem || ! $ singleDataProvidedSet ->value instanceof Array_) {
273- return [];
274- }
275-
276- foreach ($ singleDataProvidedSet ->value ->items as $ position => $ singleDataProvidedSetItem ) {
277- if ($ position !== $ parameterPosition ) {
278- continue ;
279- }
280-
281- if (! $ singleDataProvidedSetItem instanceof ArrayItem) {
282- continue ;
283- }
284-
285- $ paramOnPositionTypes [] = $ this ->nodeTypeResolver ->getType ($ singleDataProvidedSetItem ->value );
286- }
287- }
288-
289- return $ paramOnPositionTypes ;
290- }
291-
292- private function resolveDataProviderNodes (ClassMethod $ classMethod ): DataProviderNodes
293- {
294- $ attributes = $ this ->getPhpDataProviderAttributes ($ classMethod );
295-
296- $ classMethodPhpDocInfo = $ this ->phpDocInfoFactory ->createFromNode ($ classMethod );
297-
298- $ phpdocNodes = $ classMethodPhpDocInfo instanceof PhpDocInfo ?
299- $ classMethodPhpDocInfo ->getTagsByName ('@dataProvider ' ) : [];
300-
301- return new DataProviderNodes ([...$ attributes , ...$ phpdocNodes ]);
302- }
303-
304- /**
305- * @return array<array-key, Attribute>
306- */
307- private function getPhpDataProviderAttributes (ClassMethod $ classMethod ): array
308- {
309- $ attributeName = 'PHPUnit\Framework\Attributes\DataProvider ' ;
310-
311- /** @var AttributeGroup[] $attrGroups */
312- $ attrGroups = $ classMethod ->attrGroups ;
313-
314- $ dataProviders = [];
315-
316- foreach ($ attrGroups as $ attrGroup ) {
317- foreach ($ attrGroup ->attrs as $ attribute ) {
318- if (! $ this ->isName ($ attribute ->name , $ attributeName )) {
319- continue ;
320- }
321-
322- $ dataProviders [] = $ attribute ;
323- }
324- }
325-
326- return $ dataProviders ;
327- }
328-
329- /**
330- * @param array<Attribute|PhpDocTagNode> $dataProviderNodes
331- */
332- private function refactorClassMethod (ClassMethod $ classMethod , Class_ $ class , array $ dataProviderNodes ): bool
130+ private function refactorClassMethod (ClassMethod $ classMethod , DataProviderNodes $ dataProviderNodes ): bool
333131 {
334132 $ hasChanged = false ;
335133
@@ -342,20 +140,18 @@ private function refactorClassMethod(ClassMethod $classMethod, Class_ $class, ar
342140 continue ;
343141 }
344142
345- $ paramTypes = [];
346- foreach ($ dataProviderNodes as $ dataProviderNode ) {
347- $ paramTypes [] = $ this ->inferParam ($ class , $ parameterPosition , $ dataProviderNode );
348- }
349-
350- $ paramTypeDeclaration = TypeCombinator::union (...$ paramTypes );
143+ $ paramTypeDeclaration = $ this ->parameterTypeFromDataProviderResolver ->resolve (
144+ $ parameterPosition ,
145+ $ dataProviderNodes ->getClassMethods ()
146+ );
351147
352148 if ($ paramTypeDeclaration instanceof MixedType) {
353149 continue ;
354150 }
355151
356- $ type = $ this ->staticTypeMapper ->mapPHPStanTypeToPhpParserNode ($ paramTypeDeclaration , TypeKind::PARAM );
357- if ($ type instanceof Node) {
358- $ param ->type = $ type ;
152+ $ typeNode = $ this ->staticTypeMapper ->mapPHPStanTypeToPhpParserNode ($ paramTypeDeclaration , TypeKind::PARAM );
153+ if ($ typeNode instanceof Node) {
154+ $ param ->type = $ typeNode ;
359155 $ hasChanged = true ;
360156 }
361157 }
0 commit comments