Browse Source

DX: refactor PHPUnit fixers adding class-level annotation to use shared code (#6756)

Kuba Werłos 2 years ago
parent
commit
dbfd4f33e6

+ 103 - 0
src/Fixer/AbstractPhpUnitFixer.php

@@ -15,7 +15,11 @@ declare(strict_types=1);
 namespace PhpCsFixer\Fixer;
 
 use PhpCsFixer\AbstractFixer;
+use PhpCsFixer\DocBlock\DocBlock;
+use PhpCsFixer\DocBlock\Line;
 use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
+use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
+use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
 
 /**
@@ -57,8 +61,107 @@ abstract class AbstractPhpUnitFixer extends AbstractFixer
         return $index;
     }
 
+    /**
+     * @param array<string> $preventingAnnotations
+     */
+    final protected function ensureIsDockBlockWithAnnotation(
+        Tokens $tokens,
+        int $index,
+        string $annotation,
+        bool $addWithEmptyLineBeforePhpdoc,
+        bool $addWithEmptyLineBeforeAnnotation,
+        bool $addInSingleLinePhpdoc,
+        array $preventingAnnotations
+    ): void {
+        $docBlockIndex = $this->getDocBlockIndex($tokens, $index);
+
+        if ($this->isPHPDoc($tokens, $docBlockIndex)) {
+            $this->updateDocBlockIfNeeded($tokens, $docBlockIndex, $annotation, $addWithEmptyLineBeforeAnnotation, $addInSingleLinePhpdoc, $preventingAnnotations);
+        } else {
+            $this->createDocBlock($tokens, $docBlockIndex, $annotation, $addWithEmptyLineBeforePhpdoc);
+        }
+    }
+
     final protected function isPHPDoc(Tokens $tokens, int $index): bool
     {
         return $tokens[$index]->isGivenKind(T_DOC_COMMENT);
     }
+
+    private function createDocBlock(Tokens $tokens, int $docBlockIndex, string $annotation, bool $addWithEmptyLineBeforePhpdoc): void
+    {
+        $lineEnd = $this->whitespacesConfig->getLineEnding();
+        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
+        $toInsert = [
+            new Token([T_DOC_COMMENT, "/**{$lineEnd}{$originalIndent} * @{$annotation}{$lineEnd}{$originalIndent} */"]),
+            new Token([T_WHITESPACE, $lineEnd.$originalIndent]),
+        ];
+        $index = $tokens->getNextMeaningfulToken($docBlockIndex);
+        $tokens->insertAt($index, $toInsert);
+
+        if ($addWithEmptyLineBeforePhpdoc && !$tokens[$index - 1]->isGivenKind(T_WHITESPACE)) {
+            $extraNewLines = $this->whitespacesConfig->getLineEnding();
+
+            if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG)) {
+                $extraNewLines .= $this->whitespacesConfig->getLineEnding();
+            }
+
+            $tokens->insertAt($index, [
+                new Token([T_WHITESPACE, $extraNewLines.WhitespacesAnalyzer::detectIndent($tokens, $index)]),
+            ]);
+        }
+    }
+
+    /**
+     * @param array<string> $preventingAnnotations
+     */
+    private function updateDocBlockIfNeeded(
+        Tokens $tokens,
+        int $docBlockIndex,
+        string $annotation,
+        bool $addWithEmptyLineBeforeAnnotation,
+        bool $addInSingleLinePhpdoc,
+        array $preventingAnnotations
+    ): void {
+        $doc = new DocBlock($tokens[$docBlockIndex]->getContent());
+        if (!$addInSingleLinePhpdoc && 1 === \count($doc->getLines())) {
+            return;
+        }
+        foreach ($preventingAnnotations as $preventingAnnotation) {
+            if ([] !== $doc->getAnnotationsOfType($preventingAnnotation)) {
+                return;
+            }
+        }
+        $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex, $annotation);
+        $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex, $annotation, $addWithEmptyLineBeforeAnnotation);
+        $lines = implode('', $lines);
+
+        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
+    }
+
+    /**
+     * @return array<Line>
+     */
+    private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex, string $annotation, bool $addWithEmptyLineBeforeAnnotation): array
+    {
+        $lines = $docBlock->getLines();
+        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
+        $lineEnd = $this->whitespacesConfig->getLineEnding();
+        $extraLine = $addWithEmptyLineBeforeAnnotation ? $lineEnd.$originalIndent.' *' : '';
+        array_splice($lines, -1, 0, $originalIndent.' *'.$extraLine.' @'.$annotation.$lineEnd);
+
+        return $lines;
+    }
+
+    private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex, string $annotation): DocBlock
+    {
+        $lines = $doc->getLines();
+        if (1 === \count($lines) && empty($doc->getAnnotationsOfType($annotation))) {
+            $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
+            $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding());
+
+            return $doc;
+        }
+
+        return $doc;
+    }
 }

+ 9 - 69
src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php

@@ -14,8 +14,6 @@ declare(strict_types=1);
 
 namespace PhpCsFixer\Fixer\PhpUnit;
 
-use PhpCsFixer\DocBlock\DocBlock;
-use PhpCsFixer\DocBlock\Line;
 use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
 use PhpCsFixer\Fixer\ConfigurableFixerInterface;
 use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
@@ -26,8 +24,6 @@ use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
-use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
-use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
 use PhpCsFixer\Tokenizer\TokensAnalyzer;
 
@@ -63,9 +59,6 @@ final class PhpUnitInternalClassFixer extends AbstractPhpUnitFixer implements Wh
         return 68;
     }
 
-    /**
-     * {@inheritdoc}
-     */
     protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
     {
         $types = ['normal', 'final', 'abstract'];
@@ -79,9 +72,6 @@ final class PhpUnitInternalClassFixer extends AbstractPhpUnitFixer implements Wh
         ]);
     }
 
-    /**
-     * {@inheritdoc}
-     */
     protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
     {
         $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]);
@@ -90,13 +80,15 @@ final class PhpUnitInternalClassFixer extends AbstractPhpUnitFixer implements Wh
             return;
         }
 
-        $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex);
-
-        if ($this->isPHPDoc($tokens, $docBlockIndex)) {
-            $this->updateDocBlockIfNeeded($tokens, $docBlockIndex);
-        } else {
-            $this->createDocBlock($tokens, $docBlockIndex);
-        }
+        $this->ensureIsDockBlockWithAnnotation(
+            $tokens,
+            $classIndex,
+            'internal',
+            false,
+            true,
+            true,
+            ['internal']
+        );
     }
 
     private function isAllowedByConfiguration(Tokens $tokens, int $index): bool
@@ -114,56 +106,4 @@ final class PhpUnitInternalClassFixer extends AbstractPhpUnitFixer implements Wh
 
         return \in_array('normal', $this->configuration['types'], true);
     }
-
-    private function createDocBlock(Tokens $tokens, int $docBlockIndex): void
-    {
-        $lineEnd = $this->whitespacesConfig->getLineEnding();
-        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
-
-        $tokens->insertSlices([
-            $tokens->getNextMeaningfulToken($docBlockIndex) => [
-                new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @internal".$lineEnd."{$originalIndent} */"]),
-                new Token([T_WHITESPACE, $lineEnd.$originalIndent]),
-            ],
-        ]);
-    }
-
-    private function updateDocBlockIfNeeded(Tokens $tokens, int $docBlockIndex): void
-    {
-        $doc = new DocBlock($tokens[$docBlockIndex]->getContent());
-        if (!empty($doc->getAnnotationsOfType('internal'))) {
-            return;
-        }
-        $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex);
-        $lines = $this->addInternalAnnotation($doc, $tokens, $docBlockIndex);
-        $lines = implode('', $lines);
-
-        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
-    }
-
-    /**
-     * @return Line[]
-     */
-    private function addInternalAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex): array
-    {
-        $lines = $docBlock->getLines();
-        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
-        $lineEnd = $this->whitespacesConfig->getLineEnding();
-        array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @internal'.$lineEnd);
-
-        return $lines;
-    }
-
-    private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex): DocBlock
-    {
-        $lines = $doc->getLines();
-        if (1 === \count($lines) && empty($doc->getAnnotationsOfType('internal'))) {
-            $indent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
-            $doc->makeMultiLine($indent, $this->whitespacesConfig->getLineEnding());
-
-            return $doc;
-        }
-
-        return $doc;
-    }
 }

+ 12 - 127
src/Fixer/PhpUnit/PhpUnitSizeClassFixer.php

@@ -14,9 +14,6 @@ declare(strict_types=1);
 
 namespace PhpCsFixer\Fixer\PhpUnit;
 
-use PhpCsFixer\DocBlock\Annotation;
-use PhpCsFixer\DocBlock\DocBlock;
-use PhpCsFixer\DocBlock\Line;
 use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
 use PhpCsFixer\Fixer\ConfigurableFixerInterface;
 use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
@@ -26,8 +23,6 @@ use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
-use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
-use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
 
 /**
@@ -35,6 +30,8 @@ use PhpCsFixer\Tokenizer\Tokens;
  */
 final class PhpUnitSizeClassFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface
 {
+    private const SIZES = ['small', 'medium', 'large'];
+
     /**
      * {@inheritdoc}
      */
@@ -50,22 +47,16 @@ final class PhpUnitSizeClassFixer extends AbstractPhpUnitFixer implements Whites
         );
     }
 
-    /**
-     * {@inheritdoc}
-     */
     protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
     {
         return new FixerConfigurationResolver([
             (new FixerOptionBuilder('group', 'Define a specific group to be used in case no group is already in use'))
-                ->setAllowedValues(['small', 'medium', 'large'])
+                ->setAllowedValues(self::SIZES)
                 ->setDefault('small')
                 ->getOption(),
         ]);
     }
 
-    /**
-     * {@inheritdoc}
-     */
     protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
     {
         $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]);
@@ -74,13 +65,15 @@ final class PhpUnitSizeClassFixer extends AbstractPhpUnitFixer implements Whites
             return;
         }
 
-        $docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex);
-
-        if ($this->isPHPDoc($tokens, $docBlockIndex)) {
-            $this->updateDocBlockIfNeeded($tokens, $docBlockIndex);
-        } else {
-            $this->createDocBlock($tokens, $docBlockIndex);
-        }
+        $this->ensureIsDockBlockWithAnnotation(
+            $tokens,
+            $classIndex,
+            $this->configuration['group'],
+            false,
+            true,
+            true,
+            self::SIZES
+        );
     }
 
     private function isAbstractClass(Tokens $tokens, int $i): bool
@@ -89,112 +82,4 @@ final class PhpUnitSizeClassFixer extends AbstractPhpUnitFixer implements Whites
 
         return $tokens[$typeIndex]->isGivenKind(T_ABSTRACT);
     }
-
-    private function createDocBlock(Tokens $tokens, int $docBlockIndex): void
-    {
-        $lineEnd = $this->whitespacesConfig->getLineEnding();
-        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
-        $group = $this->configuration['group'];
-        $toInsert = [
-            new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @".$group.$lineEnd."{$originalIndent} */"]),
-            new Token([T_WHITESPACE, $lineEnd.$originalIndent]),
-        ];
-        $index = $tokens->getNextMeaningfulToken($docBlockIndex);
-        $tokens->insertAt($index, $toInsert);
-    }
-
-    private function updateDocBlockIfNeeded(Tokens $tokens, int $docBlockIndex): void
-    {
-        $doc = new DocBlock($tokens[$docBlockIndex]->getContent());
-
-        if (0 !== \count($this->filterDocBlock($doc))) {
-            return;
-        }
-
-        $doc = $this->makeDocBlockMultiLineIfNeeded($doc, $tokens, $docBlockIndex);
-        $lines = $this->addSizeAnnotation($doc, $tokens, $docBlockIndex);
-        $lines = implode('', $lines);
-
-        $tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
-    }
-
-    /**
-     * @return Line[]
-     */
-    private function addSizeAnnotation(DocBlock $docBlock, Tokens $tokens, int $docBlockIndex): array
-    {
-        $lines = $docBlock->getLines();
-        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
-        $lineEnd = $this->whitespacesConfig->getLineEnding();
-        $group = $this->configuration['group'];
-        array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @'.$group.$lineEnd);
-
-        return $lines;
-    }
-
-    private function makeDocBlockMultiLineIfNeeded(DocBlock $doc, Tokens $tokens, int $docBlockIndex): DocBlock
-    {
-        $lines = $doc->getLines();
-
-        if (1 === \count($lines) && 0 === \count($this->filterDocBlock($doc))) {
-            $lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex);
-
-            return new DocBlock(implode('', $lines));
-        }
-
-        return $doc;
-    }
-
-    /**
-     * Take a one line doc block, and turn it into a multi line doc block.
-     *
-     * @param Line[] $lines
-     *
-     * @return Line[]
-     */
-    private function splitUpDocBlock(array $lines, Tokens $tokens, int $docBlockIndex): array
-    {
-        $lineContent = $this->getSingleLineDocBlockEntry($lines[0]);
-        $lineEnd = $this->whitespacesConfig->getLineEnding();
-        $originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
-
-        return [
-            new Line('/**'.$lineEnd),
-            new Line($originalIndent.' * '.$lineContent.$lineEnd),
-            new Line($originalIndent.' */'),
-        ];
-    }
-
-    /**
-     * @todo check whether it's doable to use \PhpCsFixer\DocBlock\DocBlock::getSingleLineDocBlockEntry instead
-     */
-    private function getSingleLineDocBlockEntry(Line $line): string
-    {
-        $line = $line->getContent();
-        $line = str_replace('*/', '', $line);
-        $line = trim($line);
-        $line = str_split($line);
-        $i = \count($line);
-        do {
-            --$i;
-        } while ('*' !== $line[$i] && '*' !== $line[$i - 1] && '/' !== $line[$i - 2]);
-        if (' ' === $line[$i]) {
-            ++$i;
-        }
-        $line = \array_slice($line, $i);
-
-        return implode('', $line);
-    }
-
-    /**
-     * @return Annotation[][]
-     */
-    private function filterDocBlock(DocBlock $doc): array
-    {
-        return array_filter([
-            $doc->getAnnotationsOfType('small'),
-            $doc->getAnnotationsOfType('large'),
-            $doc->getAnnotationsOfType('medium'),
-        ]);
-    }
 }

+ 8 - 73
src/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixer.php

@@ -14,15 +14,11 @@ declare(strict_types=1);
 
 namespace PhpCsFixer\Fixer\PhpUnit;
 
-use PhpCsFixer\DocBlock\DocBlock;
-use PhpCsFixer\DocBlock\Line;
 use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
 use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
 use PhpCsFixer\FixerDefinition\CodeSample;
 use PhpCsFixer\FixerDefinition\FixerDefinition;
 use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
-use PhpCsFixer\Preg;
-use PhpCsFixer\Tokenizer\Token;
 use PhpCsFixer\Tokenizer\Tokens;
 use PhpCsFixer\Tokenizer\TokensAnalyzer;
 
@@ -54,9 +50,6 @@ final class MyTest extends \PHPUnit_Framework_TestCase
         );
     }
 
-    /**
-     * {@inheritdoc}
-     */
     protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
     {
         $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]);
@@ -68,76 +61,18 @@ final class MyTest extends \PHPUnit_Framework_TestCase
             return; // don't add `@covers` annotation for abstract base classes
         }
 
-        $index = $classIndex;
-
-        foreach ($modifiers as $modifier => $modifierIndex) {
-            if (null !== $modifierIndex) {
-                $index = min($index, $modifierIndex);
-            }
-        }
-
-        $indent = $tokens[$index - 1]->isGivenKind(T_WHITESPACE)
-            ? Preg::replace('/^.*\R*/', '', $tokens[$index - 1]->getContent())
-            : '';
-
-        $prevIndex = $tokens->getPrevNonWhitespace($index);
-
-        if ($tokens[$prevIndex]->isGivenKind(T_DOC_COMMENT)) {
-            $docIndex = $prevIndex;
-            $docContent = $tokens[$docIndex]->getContent();
-
-            // ignore one-line phpdocs like `/** foo */`, as there is no place to put new annotations
-            if (!str_contains($docContent, "\n")) {
-                return;
-            }
-
-            $doc = new DocBlock($docContent);
-
-            // skip if already has annotation
-            if (0 !== \count($doc->getAnnotationsOfType([
+        $this->ensureIsDockBlockWithAnnotation(
+            $tokens,
+            $classIndex,
+            'coversNothing',
+            true,
+            false,
+            false,
+            [
                 'covers',
                 'coversDefaultClass',
                 'coversNothing',
-            ]))) {
-                return;
-            }
-        } else {
-            $docIndex = $index;
-            $tokens->insertAt($docIndex, [
-                new Token([T_DOC_COMMENT, sprintf('/**%s%s */', $this->whitespacesConfig->getLineEnding(), $indent)]),
-                new Token([T_WHITESPACE, sprintf('%s%s', $this->whitespacesConfig->getLineEnding(), $indent)]),
-            ]);
-
-            if (!$tokens[$docIndex - 1]->isGivenKind(T_WHITESPACE)) {
-                $extraNewLines = $this->whitespacesConfig->getLineEnding();
-
-                if (!$tokens[$docIndex - 1]->isGivenKind(T_OPEN_TAG)) {
-                    $extraNewLines .= $this->whitespacesConfig->getLineEnding();
-                }
-
-                $tokens->insertAt($docIndex, [
-                    new Token([T_WHITESPACE, $extraNewLines.$indent]),
-                ]);
-                ++$docIndex;
-            }
-
-            $doc = new DocBlock($tokens[$docIndex]->getContent());
-        }
-
-        $lines = $doc->getLines();
-        array_splice(
-            $lines,
-            \count($lines) - 1,
-            0,
-            [
-                new Line(sprintf(
-                    '%s * @coversNothing%s',
-                    $indent,
-                    $this->whitespacesConfig->getLineEnding()
-                )),
             ]
         );
-
-        $tokens[$docIndex] = new Token([T_DOC_COMMENT, implode('', $lines)]);
     }
 }

+ 1 - 0
tests/Fixer/PhpUnit/PhpUnitSizeClassFixerTest.php

@@ -21,6 +21,7 @@ use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
  *
  * @author Jefersson Nathan <malukenho.dev@gmail.com>
  *
+ * @covers \PhpCsFixer\Fixer\AbstractPhpUnitFixer
  * @covers \PhpCsFixer\Fixer\PhpUnit\PhpUnitSizeClassFixer
  */
 final class PhpUnitSizeClassFixerTest extends AbstractFixerTestCase

+ 1 - 0
tests/Fixer/PhpUnit/PhpUnitTestClassRequiresCoversFixerTest.php

@@ -22,6 +22,7 @@ use PhpCsFixer\WhitespacesFixerConfig;
  *
  * @internal
  *
+ * @covers \PhpCsFixer\Fixer\AbstractPhpUnitFixer
  * @covers \PhpCsFixer\Fixer\PhpUnit\PhpUnitTestClassRequiresCoversFixer
  */
 final class PhpUnitTestClassRequiresCoversFixerTest extends AbstractFixerTestCase