Browse Source

feat: Store PHPDoc offset in `DataProviderAnalysis` (#8226)

Michael Voříšek 4 months ago
parent
commit
168ac89555

+ 0 - 6
dev-tools/phpstan/baseline.php

@@ -1495,12 +1495,6 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/PhpUnit/PhpUnitConstructFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: offsetAccess.notFound
-	'message' => '#^Offset 0 might not exist on list\\<int\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: method.nonObject
 	'message' => '#^Cannot call method isGivenKind\\(\\) on PhpCsFixer\\\\Tokenizer\\\\Token\\|false\\.$#',

+ 1 - 1
src/Fixer/PhpUnit/PhpUnitDataProviderNameFixer.php

@@ -153,7 +153,7 @@ class FooTest extends TestCase {
                 continue;
             }
 
-            $usageIndex = $dataProviderAnalysis->getUsageIndices()[0];
+            $usageIndex = $dataProviderAnalysis->getUsageIndices()[0][0];
             if (substr_count($tokens[$usageIndex]->getContent(), '@dataProvider') > 1) {
                 continue;
             }

+ 5 - 5
src/Tokenizer/Analyzer/Analysis/DataProviderAnalysis.php

@@ -23,17 +23,17 @@ final class DataProviderAnalysis
 
     private int $nameIndex;
 
-    /** @var list<int> */
+    /** @var non-empty-list<array{int, int}> */
     private array $usageIndices;
 
     /**
-     * @param list<int> $usageIndices
+     * @param non-empty-list<array{int, int}> $usageIndices
      */
     public function __construct(string $name, int $nameIndex, array $usageIndices)
     {
-        if (!array_is_list($usageIndices)) {
+        if ([] === $usageIndices || !array_is_list($usageIndices)) {
             Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf(
-                'Parameter "usageIndices" should be a list. This will be enforced in version %d.0.',
+                'Parameter "usageIndices" should be a non-empty-list. This will be enforced in version %d.0.',
                 Application::getMajorVersion() + 1
             )));
         }
@@ -54,7 +54,7 @@ final class DataProviderAnalysis
     }
 
     /**
-     * @return list<int>
+     * @return non-empty-list<array{int, int}>
      */
     public function getUsageIndices(): array
     {

+ 10 - 3
src/Tokenizer/Analyzer/DataProviderAnalyzer.php

@@ -41,10 +41,17 @@ final class DataProviderAnalyzer
             $docCommentIndex = $this->getDocCommentIndex($tokens, $methodIndex);
 
             if (null !== $docCommentIndex) {
-                Preg::matchAll('/@dataProvider\h+(('.self::REGEX_CLASS.'::)?'.TypeExpression::REGEX_IDENTIFIER.')/', $tokens[$docCommentIndex]->getContent(), $matches);
+                Preg::matchAll(
+                    '/@dataProvider\h+(('.self::REGEX_CLASS.'::)?'.TypeExpression::REGEX_IDENTIFIER.')/',
+                    $tokens[$docCommentIndex]->getContent(),
+                    $matches,
+                    PREG_OFFSET_CAPTURE
+                );
 
-                foreach ($matches[1] as $dataProviderName) {
-                    $dataProviders[$dataProviderName][] = $docCommentIndex;
+                foreach ($matches[1] as $k => [$matchName]) {
+                    \assert(isset($matches[0][$k]));
+
+                    $dataProviders[$matchName][] = [$docCommentIndex, $matches[0][$k][1]];
                 }
             }
         }

+ 2 - 2
tests/Tokenizer/Analyzer/Analysis/DataProviderAnalysisTest.php

@@ -26,10 +26,10 @@ final class DataProviderAnalysisTest extends TestCase
 {
     public function testDataProviderAnalysis(): void
     {
-        $analysis = new DataProviderAnalysis('Foo', 1, [2, 3]);
+        $analysis = new DataProviderAnalysis('Foo', 1, [[2, 10], [3, 11]]);
 
         self::assertSame('Foo', $analysis->getName());
         self::assertSame(1, $analysis->getNameIndex());
-        self::assertSame([2, 3], $analysis->getUsageIndices());
+        self::assertSame([[2, 10], [3, 11]], $analysis->getUsageIndices());
     }
 }

+ 28 - 11
tests/Tokenizer/Analyzer/DataProviderAnalyzerTest.php

@@ -48,7 +48,7 @@ final class DataProviderAnalyzerTest extends TestCase
     public static function provideGettingDataProvidersCases(): iterable
     {
         yield 'single data provider' => [
-            [new DataProviderAnalysis('provider', 28, [11])],
+            [new DataProviderAnalysis('provider', 28, [[11, 23]])],
             '<?php class FooTest extends TestCase {
                 /**
                  * @dataProvider provider
@@ -59,7 +59,7 @@ final class DataProviderAnalyzerTest extends TestCase
         ];
 
         yield 'single data provider with different casing' => [
-            [new DataProviderAnalysis('dataProvider', 28, [11])],
+            [new DataProviderAnalysis('dataProvider', 28, [[11, 23]])],
             '<?php class FooTest extends TestCase {
                 /**
                  * @dataProvider dataPROVIDER
@@ -70,7 +70,7 @@ final class DataProviderAnalyzerTest extends TestCase
         ];
 
         yield 'single static data provider' => [
-            [new DataProviderAnalysis('provider', 30, [11])],
+            [new DataProviderAnalysis('provider', 30, [[11, 23]])],
             '<?php class FooTest extends TestCase {
                 /**
                  * @dataProvider provider
@@ -82,9 +82,9 @@ final class DataProviderAnalyzerTest extends TestCase
 
         yield 'multiple data provider' => [
             [
-                new DataProviderAnalysis('provider1', 28, [11]),
-                new DataProviderAnalysis('provider2', 39, [11]),
-                new DataProviderAnalysis('provider3', 50, [11]),
+                new DataProviderAnalysis('provider1', 28, [[11, 23]]),
+                new DataProviderAnalysis('provider2', 39, [[11, 66]]),
+                new DataProviderAnalysis('provider3', 50, [[11, 109]]),
             ],
             '<?php class FooTest extends TestCase {
                 /**
@@ -99,12 +99,29 @@ final class DataProviderAnalyzerTest extends TestCase
             }',
         ];
 
+        yield 'single data provider with multiple usage' => [
+            [
+                new DataProviderAnalysis('provider', 28, [[11, 23], [35, 23]]),
+            ],
+            '<?php class FooTest extends TestCase {
+                /**
+                 * @dataProvider provider
+                 */
+                public function testFoo() {}
+                public function provider() {}
+                /**
+                 * @dataProvider provider
+                 */
+                public function testFoo2() {}
+            }',
+        ];
+
         foreach (['abstract', 'final', 'private', 'protected', 'static', '/* private */'] as $modifier) {
             yield \sprintf('test function with %s modifier', $modifier) => [
                 [
-                    new DataProviderAnalysis('provider1', 54, [37]),
-                    new DataProviderAnalysis('provider2', 65, [11]),
-                    new DataProviderAnalysis('provider3', 76, [24]),
+                    new DataProviderAnalysis('provider1', 54, [[37, 4]]),
+                    new DataProviderAnalysis('provider2', 65, [[11, 4]]),
+                    new DataProviderAnalysis('provider3', 76, [[24, 4]]),
                 ],
                 \sprintf('<?php class FooTest extends TestCase {
                     /** @dataProvider provider2 */
@@ -143,7 +160,7 @@ final class DataProviderAnalyzerTest extends TestCase
 
         yield 'ignore anonymous function' => [
             [
-                new DataProviderAnalysis('provider2', 93, [65]),
+                new DataProviderAnalysis('provider2', 93, [[65, 27]]),
             ],
             '<?php class FooTest extends TestCase {
                 public function testFoo0() {}
@@ -185,7 +202,7 @@ final class DataProviderAnalyzerTest extends TestCase
     public static function provideGettingDataProviders80Cases(): iterable
     {
         yield 'with an attribute between PHPDoc and test method' => [
-            [new DataProviderAnalysis('provideFooCases', 35, [11])],
+            [new DataProviderAnalysis('provideFooCases', 35, [[11, 11]])],
             <<<'PHP'
                 <?php
                 class FooTest extends TestCase {