16
16
use Nexus \Option \None ;
17
17
use Nexus \Option \Some ;
18
18
use Nexus \Tests \Option \OptionTest ;
19
+ use PHPStan \Testing \TypeInferenceTestCase ;
19
20
use PHPUnit \Framework \Attributes \CoversClass ;
20
21
use PHPUnit \Framework \Attributes \CoversFunction ;
21
22
use PHPUnit \Framework \Attributes \CoversNothing ;
@@ -36,6 +37,7 @@ final class TestCodeTest extends TestCase
36
37
private const RECOGNISED_GROUP_NAMES = [
37
38
'auto-review ' ,
38
39
'package-test ' ,
40
+ 'static-analysis ' ,
39
41
'unit-test ' ,
40
42
];
41
43
@@ -182,7 +184,7 @@ public function testDataProvidersDeclareCorrectReturnType(string $testClassName,
182
184
self ::assertMatchesRegularExpression (
183
185
'/@return iterable<(?:class-)?string(?:\<\S+\>)?, array\{/ ' ,
184
186
$ docComment ,
185
- \sprintf ('Return PHPDoc of data provider "%s::%s" must be an iterable of named array shape (i.e ., iterable<string, array{string}>). ' , $ testClassName , $ dataProviderMethod ),
187
+ \sprintf ('Return PHPDoc of data provider "%s::%s" must be an iterable of named array shape (e.g ., iterable<string, array{string}>). ' , $ testClassName , $ dataProviderMethod ),
186
188
);
187
189
}
188
190
@@ -329,4 +331,59 @@ public static function provideTestClassCases(): iterable
329
331
yield $ class => [$ class ];
330
332
}
331
333
}
334
+
335
+ #[DataProvider('provideGenericClassHasTypeInferenceTestForNamespaceCases ' )]
336
+ public function testGenericClassHasTypeInferenceTestForNamespace (string $ package ): void
337
+ {
338
+ $ expectedTypeInferentTest = \sprintf ('Nexus \\Tests \\%1$s \\%1$sTypeInferenceTest ' , $ package );
339
+
340
+ self ::assertTrue (class_exists ($ expectedTypeInferentTest ), \sprintf (
341
+ 'The %s package has generic class(es) thus it requires a %s. ' ,
342
+ $ package ,
343
+ $ expectedTypeInferentTest ,
344
+ ));
345
+ self ::assertTrue (is_subclass_of ($ expectedTypeInferentTest , TypeInferenceTestCase::class), \sprintf (
346
+ 'Type inference test "%s" should extend %s. ' ,
347
+ $ expectedTypeInferentTest ,
348
+ TypeInferenceTestCase::class,
349
+ ));
350
+
351
+ $ groupAttributes = array_map (static function (\ReflectionAttribute $ attribute ): string {
352
+ $ groupAttribute = $ attribute ->newInstance ();
353
+ \assert ($ groupAttribute instanceof Group);
354
+
355
+ return $ groupAttribute ->name ();
356
+ }, (new \ReflectionClass ($ expectedTypeInferentTest ))->getAttributes (Group::class));
357
+ self ::assertContains ('static-analysis ' , $ groupAttributes , \sprintf (
358
+ 'Test "%s" should have the #[Group( \'static-analysis \')] attribute. ' ,
359
+ $ expectedTypeInferentTest ,
360
+ ));
361
+ }
362
+
363
+ /**
364
+ * @return iterable<string, array{string}>
365
+ */
366
+ public static function provideGenericClassHasTypeInferenceTestForNamespaceCases (): iterable
367
+ {
368
+ $ packages = [];
369
+
370
+ foreach (self ::getSourceClasses () as $ class ) {
371
+ $ reflection = new \ReflectionClass ($ class );
372
+ $ docComment = $ reflection ->getDocComment ();
373
+
374
+ if (false === $ docComment || ! str_contains ($ docComment , '* @template ' )) {
375
+ continue ;
376
+ }
377
+
378
+ $ package = explode ('\\' , $ reflection ->getNamespaceName ())[1 ];
379
+
380
+ if (\array_key_exists ($ package , $ packages )) {
381
+ continue ;
382
+ }
383
+
384
+ $ packages [$ package ] = true ;
385
+
386
+ yield $ package => [$ package ];
387
+ }
388
+ }
332
389
}
0 commit comments