PhpUnitTestClassRequiresCoversFixer.php 4.48 KB
Newer Older
Ketan's avatar
Ketan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
<?php

/*
 * This file is part of PHP CS Fixer.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace PhpCsFixer\Fixer\PhpUnit;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\DocBlock\DocBlock;
use PhpCsFixer\DocBlock\Line;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Indicator\PhpUnitTestCaseIndicator;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 */
final class PhpUnitTestClassRequiresCoversFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
{
    /**
     * {@inheritdoc}
     */
    public function getDefinition()
    {
        return new FixerDefinition(
            'Adds a default `@coversNothing` annotation to PHPUnit test classes that have no `@covers*` annotation.',
            [
                new CodeSample(
'<?php
final class MyTest extends \PHPUnit_Framework_TestCase
{
    public function testSomeTest()
    {
        $this->assertSame(a(), b());
    }
}
'
                ),
            ]
        );
    }

    /**
     * {@inheritdoc}
     */
    public function isCandidate(Tokens $tokens)
    {
        return $tokens->isTokenKindFound(T_CLASS);
    }

    /**
     * {@inheritdoc}
     */
    protected function applyFix(\SplFileInfo $file, Tokens $tokens)
    {
        $phpUnitTestCaseIndicator = new PhpUnitTestCaseIndicator();

        foreach ($phpUnitTestCaseIndicator->findPhpUnitClasses($tokens) as $indexes) {
            $this->addRequiresCover($tokens, $indexes[0]);
        }
    }

    private function addRequiresCover(Tokens $tokens, $startIndex)
    {
        $classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]);
        $prevIndex = $tokens->getPrevMeaningfulToken($classIndex);

        // don't add `@covers` annotation for abstract base classes
        if ($tokens[$prevIndex]->isGivenKind(T_ABSTRACT)) {
            return;
        }

        $index = $tokens[$prevIndex]->isGivenKind(T_FINAL) ? $prevIndex : $classIndex;

        $indent = $tokens[$index - 1]->isGivenKind(T_WHITESPACE)
            ? Preg::replace('/^.*\R*/', '', $tokens[$index - 1]->getContent())
            : '';

        $prevIndex = $tokens->getPrevNonWhitespace($index);
        $doc = null;
        $docIndex = null;

        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 (false === strpos($docContent, "\n")) {
                return;
            }

            $doc = new DocBlock($docContent);

            // skip if already has annotation
            if (!empty($doc->getAnnotationsOfType([
                '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)]);
    }
}