Missing dependancies
This commit is contained in:
+210
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitConstructFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private static array $assertionFixers = [
|
||||
'assertSame' => 'fixAssertPositive',
|
||||
'assertEquals' => 'fixAssertPositive',
|
||||
'assertNotEquals' => 'fixAssertNegative',
|
||||
'assertNotSame' => 'fixAssertNegative',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPUnit assertion method calls like `->assertSame(true, $foo)` should be written with dedicated method like `->assertTrue($foo)`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class FooTest extends \PHPUnit_Framework_TestCase {
|
||||
public function testSomething() {
|
||||
$this->assertEquals(false, $b);
|
||||
$this->assertSame(true, $a);
|
||||
$this->assertNotEquals(null, $c);
|
||||
$this->assertNotSame(null, $d);
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class FooTest extends \PHPUnit_Framework_TestCase {
|
||||
public function testSomething() {
|
||||
$this->assertEquals(false, $b);
|
||||
$this->assertSame(true, $a);
|
||||
$this->assertNotEquals(null, $c);
|
||||
$this->assertNotSame(null, $d);
|
||||
}
|
||||
}
|
||||
',
|
||||
['assertions' => ['assertSame', 'assertNotSame']]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Fixer could be risky if one is overriding PHPUnit\'s native methods.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before PhpUnitDedicateAssertFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -8;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
// no assertions to be fixed - fast return
|
||||
if (empty($this->configuration['assertions'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->configuration['assertions'] as $assertionMethod) {
|
||||
$assertionFixer = self::$assertionFixers[$assertionMethod];
|
||||
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
$index = $this->{$assertionFixer}($tokens, $index, $assertionMethod);
|
||||
|
||||
if (null === $index) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('assertions', 'List of assertion methods to fix.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionFixers))])
|
||||
->setDefault([
|
||||
'assertEquals',
|
||||
'assertSame',
|
||||
'assertNotEquals',
|
||||
'assertNotSame',
|
||||
])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function fixAssertNegative(Tokens $tokens, int $index, string $method): ?int
|
||||
{
|
||||
static $map = [
|
||||
'false' => 'assertNotFalse',
|
||||
'null' => 'assertNotNull',
|
||||
'true' => 'assertNotTrue',
|
||||
];
|
||||
|
||||
return $this->fixAssert($map, $tokens, $index, $method);
|
||||
}
|
||||
|
||||
private function fixAssertPositive(Tokens $tokens, int $index, string $method): ?int
|
||||
{
|
||||
static $map = [
|
||||
'false' => 'assertFalse',
|
||||
'null' => 'assertNull',
|
||||
'true' => 'assertTrue',
|
||||
];
|
||||
|
||||
return $this->fixAssert($map, $tokens, $index, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $map
|
||||
*/
|
||||
private function fixAssert(array $map, Tokens $tokens, int $index, string $method): ?int
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
$sequence = $tokens->findSequence(
|
||||
[
|
||||
[T_STRING, $method],
|
||||
'(',
|
||||
],
|
||||
$index
|
||||
);
|
||||
|
||||
if (null === $sequence) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sequenceIndices = array_keys($sequence);
|
||||
|
||||
if (!$functionsAnalyzer->isTheSameClassCall($tokens, $sequenceIndices[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sequenceIndices[2] = $tokens->getNextMeaningfulToken($sequenceIndices[1]);
|
||||
$firstParameterToken = $tokens[$sequenceIndices[2]];
|
||||
|
||||
if (!$firstParameterToken->isNativeConstant()) {
|
||||
return $sequenceIndices[2];
|
||||
}
|
||||
|
||||
$sequenceIndices[3] = $tokens->getNextMeaningfulToken($sequenceIndices[2]);
|
||||
|
||||
// return if first method argument is an expression, not value
|
||||
if (!$tokens[$sequenceIndices[3]]->equals(',')) {
|
||||
return $sequenceIndices[3];
|
||||
}
|
||||
|
||||
$tokens[$sequenceIndices[0]] = new Token([T_STRING, $map[strtolower($firstParameterToken->getContent())]]);
|
||||
$tokens->clearRange($sequenceIndices[2], $tokens->getNextNonWhitespace($sequenceIndices[3]) - 1);
|
||||
|
||||
return $sequenceIndices[3];
|
||||
}
|
||||
}
|
||||
+643
@@ -0,0 +1,643 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitDedicateAssertFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<string, bool|int|string>|true>
|
||||
*/
|
||||
private static array $fixMap = [
|
||||
'array_key_exists' => [
|
||||
'positive' => 'assertArrayHasKey',
|
||||
'negative' => 'assertArrayNotHasKey',
|
||||
'argument_count' => 2,
|
||||
],
|
||||
'empty' => [
|
||||
'positive' => 'assertEmpty',
|
||||
'negative' => 'assertNotEmpty',
|
||||
],
|
||||
'file_exists' => [
|
||||
'positive' => 'assertFileExists',
|
||||
'negative' => 'assertFileNotExists',
|
||||
],
|
||||
'is_array' => true,
|
||||
'is_bool' => true,
|
||||
'is_callable' => true,
|
||||
'is_dir' => [
|
||||
'positive' => 'assertDirectoryExists',
|
||||
'negative' => 'assertDirectoryNotExists',
|
||||
],
|
||||
'is_double' => true,
|
||||
'is_float' => true,
|
||||
'is_infinite' => [
|
||||
'positive' => 'assertInfinite',
|
||||
'negative' => 'assertFinite',
|
||||
],
|
||||
'is_int' => true,
|
||||
'is_integer' => true,
|
||||
'is_long' => true,
|
||||
'is_nan' => [
|
||||
'positive' => 'assertNan',
|
||||
'negative' => false,
|
||||
],
|
||||
'is_null' => [
|
||||
'positive' => 'assertNull',
|
||||
'negative' => 'assertNotNull',
|
||||
],
|
||||
'is_numeric' => true,
|
||||
'is_object' => true,
|
||||
'is_readable' => [
|
||||
'positive' => 'assertIsReadable',
|
||||
'negative' => 'assertNotIsReadable',
|
||||
],
|
||||
'is_real' => true,
|
||||
'is_resource' => true,
|
||||
'is_scalar' => true,
|
||||
'is_string' => true,
|
||||
'is_writable' => [
|
||||
'positive' => 'assertIsWritable',
|
||||
'negative' => 'assertNotIsWritable',
|
||||
],
|
||||
'str_contains' => [ // since 7.5
|
||||
'positive' => 'assertStringContainsString',
|
||||
'negative' => 'assertStringNotContainsString',
|
||||
'argument_count' => 2,
|
||||
'swap_arguments' => true,
|
||||
],
|
||||
'str_ends_with' => [ // since 3.4
|
||||
'positive' => 'assertStringEndsWith',
|
||||
'negative' => 'assertStringEndsNotWith',
|
||||
'argument_count' => 2,
|
||||
'swap_arguments' => true,
|
||||
],
|
||||
'str_starts_with' => [ // since 3.4
|
||||
'positive' => 'assertStringStartsWith',
|
||||
'negative' => 'assertStringStartsNotWith',
|
||||
'argument_count' => 2,
|
||||
'swap_arguments' => true,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $functions = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
// assertions added in 3.0: assertArrayNotHasKey assertArrayHasKey assertFileNotExists assertFileExists assertNotNull, assertNull
|
||||
$this->functions = [
|
||||
'array_key_exists',
|
||||
'file_exists',
|
||||
'is_null',
|
||||
'str_ends_with',
|
||||
'str_starts_with',
|
||||
];
|
||||
|
||||
if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_3_5)) {
|
||||
// assertions added in 3.5: assertInternalType assertNotEmpty assertEmpty
|
||||
$this->functions = array_merge($this->functions, [
|
||||
'empty',
|
||||
'is_array',
|
||||
'is_bool',
|
||||
'is_boolean',
|
||||
'is_callable',
|
||||
'is_double',
|
||||
'is_float',
|
||||
'is_int',
|
||||
'is_integer',
|
||||
'is_long',
|
||||
'is_numeric',
|
||||
'is_object',
|
||||
'is_real',
|
||||
'is_scalar',
|
||||
'is_string',
|
||||
]);
|
||||
}
|
||||
|
||||
if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_0)) {
|
||||
// assertions added in 5.0: assertFinite assertInfinite assertNan
|
||||
$this->functions = array_merge($this->functions, [
|
||||
'is_infinite',
|
||||
'is_nan',
|
||||
]);
|
||||
}
|
||||
|
||||
if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) {
|
||||
// assertions added in 5.6: assertDirectoryExists assertDirectoryNotExists assertIsReadable assertNotIsReadable assertIsWritable assertNotIsWritable
|
||||
$this->functions = array_merge($this->functions, [
|
||||
'is_dir',
|
||||
'is_readable',
|
||||
'is_writable',
|
||||
]);
|
||||
}
|
||||
|
||||
if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_7_5)) {
|
||||
$this->functions = array_merge($this->functions, [
|
||||
'str_contains',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPUnit assertions like `assertInternalType`, `assertFileExists`, should be used over `assertTrue`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSomeTest()
|
||||
{
|
||||
$this->assertTrue(is_float( $a), "my message");
|
||||
$this->assertTrue(is_nan($a));
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSomeTest()
|
||||
{
|
||||
$this->assertTrue(is_dir($a));
|
||||
$this->assertTrue(is_writable($a));
|
||||
$this->assertTrue(is_readable($a));
|
||||
}
|
||||
}
|
||||
',
|
||||
['target' => PhpUnitTargetVersion::VERSION_5_6]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Fixer could be risky if one is overriding PHPUnit\'s native methods.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoUnusedImportsFixer, PhpUnitDedicateAssertInternalTypeFixer.
|
||||
* Must run after ModernizeStrposFixer, NoAliasFunctionsFixer, PhpUnitConstructFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -9;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
foreach ($this->getPreviousAssertCall($tokens, $startIndex, $endIndex) as $assertCall) {
|
||||
// test and fix for assertTrue/False to dedicated asserts
|
||||
if ('asserttrue' === $assertCall['loweredName'] || 'assertfalse' === $assertCall['loweredName']) {
|
||||
$this->fixAssertTrueFalse($tokens, $argumentsAnalyzer, $assertCall);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
'assertsame' === $assertCall['loweredName']
|
||||
|| 'assertnotsame' === $assertCall['loweredName']
|
||||
|| 'assertequals' === $assertCall['loweredName']
|
||||
|| 'assertnotequals' === $assertCall['loweredName']
|
||||
) {
|
||||
$this->fixAssertSameEquals($tokens, $assertCall);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([
|
||||
PhpUnitTargetVersion::VERSION_3_0,
|
||||
PhpUnitTargetVersion::VERSION_3_5,
|
||||
PhpUnitTargetVersion::VERSION_5_0,
|
||||
PhpUnitTargetVersion::VERSION_5_6,
|
||||
PhpUnitTargetVersion::VERSION_NEWEST,
|
||||
])
|
||||
->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* index: int,
|
||||
* loweredName: string,
|
||||
* openBraceIndex: int,
|
||||
* closeBraceIndex: int,
|
||||
* } $assertCall
|
||||
*/
|
||||
private function fixAssertTrueFalse(Tokens $tokens, ArgumentsAnalyzer $argumentsAnalyzer, array $assertCall): void
|
||||
{
|
||||
$testDefaultNamespaceTokenIndex = null;
|
||||
$testIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']);
|
||||
|
||||
if (!$tokens[$testIndex]->isGivenKind([T_EMPTY, T_STRING])) {
|
||||
if ($this->fixAssertTrueFalseInstanceof($tokens, $assertCall, $testIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$tokens[$testIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$testDefaultNamespaceTokenIndex = $testIndex;
|
||||
$testIndex = $tokens->getNextMeaningfulToken($testIndex);
|
||||
}
|
||||
|
||||
$testOpenIndex = $tokens->getNextMeaningfulToken($testIndex);
|
||||
|
||||
if (!$tokens[$testOpenIndex]->equals('(')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$testCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $testOpenIndex);
|
||||
$assertCallCloseIndex = $tokens->getNextMeaningfulToken($testCloseIndex);
|
||||
|
||||
if (!$tokens[$assertCallCloseIndex]->equalsAny([')', ','])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = strtolower($tokens[$testIndex]->getContent());
|
||||
|
||||
if (!\in_array($content, $this->functions, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$arguments = $argumentsAnalyzer->getArguments($tokens, $testOpenIndex, $testCloseIndex);
|
||||
$isPositive = 'asserttrue' === $assertCall['loweredName'];
|
||||
|
||||
if (\is_array(self::$fixMap[$content])) {
|
||||
$expectedCount = self::$fixMap[$content]['argument_count'] ?? 1;
|
||||
|
||||
if ($expectedCount !== \count($arguments)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$isPositive = $isPositive ? 'positive' : 'negative';
|
||||
|
||||
if (false === self::$fixMap[$content][$isPositive]) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tokens[$assertCall['index']] = new Token([T_STRING, self::$fixMap[$content][$isPositive]]);
|
||||
$this->removeFunctionCall($tokens, $testDefaultNamespaceTokenIndex, $testIndex, $testOpenIndex, $testCloseIndex);
|
||||
|
||||
if (self::$fixMap[$content]['swap_arguments'] ?? false) {
|
||||
if (2 !== $expectedCount) {
|
||||
throw new \RuntimeException('Can only swap two arguments, please update map or logic.');
|
||||
}
|
||||
|
||||
$this->swapArguments($tokens, $arguments);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (1 !== \count($arguments)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = substr($content, 3);
|
||||
|
||||
$tokens[$assertCall['index']] = new Token([T_STRING, $isPositive ? 'assertInternalType' : 'assertNotInternalType']);
|
||||
$tokens[$testIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$type."'"]);
|
||||
$tokens[$testOpenIndex] = new Token(',');
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($testCloseIndex);
|
||||
$commaIndex = $tokens->getPrevMeaningfulToken($testCloseIndex);
|
||||
|
||||
if ($tokens[$commaIndex]->equals(',')) {
|
||||
$tokens->removeTrailingWhitespace($commaIndex);
|
||||
$tokens->clearAt($commaIndex);
|
||||
}
|
||||
|
||||
if (!$tokens[$testOpenIndex + 1]->isWhitespace()) {
|
||||
$tokens->insertAt($testOpenIndex + 1, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
|
||||
if (null !== $testDefaultNamespaceTokenIndex) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($testDefaultNamespaceTokenIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* index: int,
|
||||
* loweredName: string,
|
||||
* openBraceIndex: int,
|
||||
* closeBraceIndex: int,
|
||||
* } $assertCall
|
||||
*/
|
||||
private function fixAssertTrueFalseInstanceof(Tokens $tokens, array $assertCall, int $testIndex): bool
|
||||
{
|
||||
if ($tokens[$testIndex]->equals('!')) {
|
||||
$variableIndex = $tokens->getNextMeaningfulToken($testIndex);
|
||||
$positive = false;
|
||||
} else {
|
||||
$variableIndex = $testIndex;
|
||||
$positive = true;
|
||||
}
|
||||
|
||||
if (!$tokens[$variableIndex]->isGivenKind(T_VARIABLE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$instanceOfIndex = $tokens->getNextMeaningfulToken($variableIndex);
|
||||
|
||||
if (!$tokens[$instanceOfIndex]->isGivenKind(T_INSTANCEOF)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$classEndIndex = $instanceOfIndex;
|
||||
$classPartTokens = [];
|
||||
|
||||
do {
|
||||
$classEndIndex = $tokens->getNextMeaningfulToken($classEndIndex);
|
||||
$classPartTokens[] = $tokens[$classEndIndex];
|
||||
} while ($tokens[$classEndIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR, T_VARIABLE]));
|
||||
|
||||
if ($tokens[$classEndIndex]->equalsAny([',', ')'])) { // do the fixing
|
||||
array_pop($classPartTokens);
|
||||
$isInstanceOfVar = reset($classPartTokens)->isGivenKind(T_VARIABLE);
|
||||
$insertIndex = $testIndex - 1;
|
||||
$newTokens = [];
|
||||
|
||||
foreach ($classPartTokens as $token) {
|
||||
$newTokens[++$insertIndex] = clone $token;
|
||||
}
|
||||
|
||||
if (!$isInstanceOfVar) {
|
||||
$newTokens[++$insertIndex] = new Token([T_DOUBLE_COLON, '::']);
|
||||
$newTokens[++$insertIndex] = new Token([CT::T_CLASS_CONSTANT, 'class']);
|
||||
}
|
||||
|
||||
$newTokens[++$insertIndex] = new Token(',');
|
||||
$newTokens[++$insertIndex] = new Token([T_WHITESPACE, ' ']);
|
||||
$newTokens[++$insertIndex] = clone $tokens[$variableIndex];
|
||||
|
||||
for ($i = $classEndIndex - 1; $i >= $testIndex; --$i) {
|
||||
if (!$tokens[$i]->isComment()) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($i);
|
||||
}
|
||||
}
|
||||
|
||||
$tokens->insertSlices($newTokens);
|
||||
$tokens[$assertCall['index']] = new Token([T_STRING, $positive ? 'assertInstanceOf' : 'assertNotInstanceOf']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* index: int,
|
||||
* loweredName: string,
|
||||
* openBraceIndex: int,
|
||||
* closeBraceIndex: int,
|
||||
* } $assertCall
|
||||
*/
|
||||
private function fixAssertSameEquals(Tokens $tokens, array $assertCall): void
|
||||
{
|
||||
// @ $this->/self::assertEquals/Same([$nextIndex])
|
||||
$expectedIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']);
|
||||
|
||||
// do not fix
|
||||
// let $a = [1,2]; $b = "2";
|
||||
// "$this->assertEquals("2", count($a)); $this->assertEquals($b, count($a)); $this->assertEquals(2.1, count($a));"
|
||||
|
||||
if ($tokens[$expectedIndex]->isGivenKind([T_VARIABLE])) {
|
||||
if (!$tokens[$tokens->getNextMeaningfulToken($expectedIndex)]->equals(',')) {
|
||||
return;
|
||||
}
|
||||
} elseif (!$tokens[$expectedIndex]->isGivenKind([T_LNUMBER, T_VARIABLE])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex])
|
||||
$commaIndex = $tokens->getNextMeaningfulToken($expectedIndex);
|
||||
|
||||
if (!$tokens[$commaIndex]->equals(',')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex,$countCallIndex])
|
||||
$countCallIndex = $tokens->getNextMeaningfulToken($commaIndex);
|
||||
|
||||
if ($tokens[$countCallIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$defaultNamespaceTokenIndex = $countCallIndex;
|
||||
$countCallIndex = $tokens->getNextMeaningfulToken($countCallIndex);
|
||||
} else {
|
||||
$defaultNamespaceTokenIndex = null;
|
||||
}
|
||||
|
||||
if (!$tokens[$countCallIndex]->isGivenKind(T_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lowerContent = strtolower($tokens[$countCallIndex]->getContent());
|
||||
|
||||
if ('count' !== $lowerContent && 'sizeof' !== $lowerContent) {
|
||||
return; // not a call to "count" or "sizeOf"
|
||||
}
|
||||
|
||||
// @ $this->/self::assertEquals/Same([$nextIndex,$commaIndex,[$defaultNamespaceTokenIndex,]$countCallIndex,$countCallOpenBraceIndex])
|
||||
$countCallOpenBraceIndex = $tokens->getNextMeaningfulToken($countCallIndex);
|
||||
|
||||
if (!$tokens[$countCallOpenBraceIndex]->equals('(')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$countCallCloseBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $countCallOpenBraceIndex);
|
||||
$afterCountCallCloseBraceIndex = $tokens->getNextMeaningfulToken($countCallCloseBraceIndex);
|
||||
|
||||
if (!$tokens[$afterCountCallCloseBraceIndex]->equalsAny([')', ','])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->removeFunctionCall(
|
||||
$tokens,
|
||||
$defaultNamespaceTokenIndex,
|
||||
$countCallIndex,
|
||||
$countCallOpenBraceIndex,
|
||||
$countCallCloseBraceIndex
|
||||
);
|
||||
|
||||
$tokens[$assertCall['index']] = new Token([
|
||||
T_STRING,
|
||||
false === strpos($assertCall['loweredName'], 'not', 6) ? 'assertCount' : 'assertNotCount',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array{
|
||||
* index: int,
|
||||
* loweredName: string,
|
||||
* openBraceIndex: int,
|
||||
* closeBraceIndex: int,
|
||||
* }>
|
||||
*/
|
||||
private function getPreviousAssertCall(Tokens $tokens, int $startIndex, int $endIndex): iterable
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
for ($index = $endIndex; $index > $startIndex; --$index) {
|
||||
$index = $tokens->getPrevTokenOfKind($index, [[T_STRING]]);
|
||||
|
||||
if (null === $index) {
|
||||
return;
|
||||
}
|
||||
|
||||
// test if "assert" something call
|
||||
$loweredContent = strtolower($tokens[$index]->getContent());
|
||||
|
||||
if (!str_starts_with($loweredContent, 'assert')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// test candidate for simple calls like: ([\]+'some fixable call'(...))
|
||||
$openBraceIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$openBraceIndex]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$functionsAnalyzer->isTheSameClassCall($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield [
|
||||
'index' => $index,
|
||||
'loweredName' => $loweredContent,
|
||||
'openBraceIndex' => $openBraceIndex,
|
||||
'closeBraceIndex' => $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBraceIndex),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function removeFunctionCall(Tokens $tokens, ?int $callNSIndex, int $callIndex, int $openIndex, int $closeIndex): void
|
||||
{
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($callIndex);
|
||||
|
||||
if (null !== $callNSIndex) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($callNSIndex);
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($openIndex);
|
||||
$commaIndex = $tokens->getPrevMeaningfulToken($closeIndex);
|
||||
|
||||
if ($tokens[$commaIndex]->equals(',')) {
|
||||
$tokens->removeTrailingWhitespace($commaIndex);
|
||||
$tokens->clearAt($commaIndex);
|
||||
}
|
||||
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($closeIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $argumentsIndices
|
||||
*/
|
||||
private function swapArguments(Tokens $tokens, array $argumentsIndices): void
|
||||
{
|
||||
[$firstArgumentIndex, $secondArgumentIndex] = array_keys($argumentsIndices);
|
||||
|
||||
$firstArgumentEndIndex = $argumentsIndices[$firstArgumentIndex];
|
||||
$secondArgumentEndIndex = $argumentsIndices[$secondArgumentIndex];
|
||||
|
||||
$firstClone = $this->cloneAndClearTokens($tokens, $firstArgumentIndex, $firstArgumentEndIndex);
|
||||
$secondClone = $this->cloneAndClearTokens($tokens, $secondArgumentIndex, $secondArgumentEndIndex);
|
||||
|
||||
if (!$firstClone[0]->isWhitespace()) {
|
||||
array_unshift($firstClone, new Token([T_WHITESPACE, ' ']));
|
||||
}
|
||||
|
||||
$tokens->insertAt($secondArgumentIndex, $firstClone);
|
||||
|
||||
if ($secondClone[0]->isWhitespace()) {
|
||||
array_shift($secondClone);
|
||||
}
|
||||
|
||||
$tokens->insertAt($firstArgumentIndex, $secondClone);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Token>
|
||||
*/
|
||||
private function cloneAndClearTokens(Tokens $tokens, int $start, int $end): array
|
||||
{
|
||||
$clone = [];
|
||||
|
||||
for ($i = $start; $i <= $end; ++$i) {
|
||||
if ('' === $tokens[$i]->getContent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$clone[] = clone $tokens[$i];
|
||||
$tokens->clearAt($i);
|
||||
}
|
||||
|
||||
return $clone;
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class PhpUnitDedicateAssertInternalTypeFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $typeToDedicatedAssertMap = [
|
||||
'array' => 'assertIsArray',
|
||||
'boolean' => 'assertIsBool',
|
||||
'bool' => 'assertIsBool',
|
||||
'double' => 'assertIsFloat',
|
||||
'float' => 'assertIsFloat',
|
||||
'integer' => 'assertIsInt',
|
||||
'int' => 'assertIsInt',
|
||||
'null' => 'assertNull',
|
||||
'numeric' => 'assertIsNumeric',
|
||||
'object' => 'assertIsObject',
|
||||
'real' => 'assertIsFloat',
|
||||
'resource' => 'assertIsResource',
|
||||
'string' => 'assertIsString',
|
||||
'scalar' => 'assertIsScalar',
|
||||
'callable' => 'assertIsCallable',
|
||||
'iterable' => 'assertIsIterable',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPUnit assertions like `assertIsArray` should be used over `assertInternalType`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testMe()
|
||||
{
|
||||
$this->assertInternalType("array", $var);
|
||||
$this->assertInternalType("boolean", $var);
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testMe()
|
||||
{
|
||||
$this->assertInternalType("array", $var);
|
||||
$this->assertInternalType("boolean", $var);
|
||||
}
|
||||
}
|
||||
',
|
||||
['target' => PhpUnitTargetVersion::VERSION_7_5]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when PHPUnit methods are overridden or when project has PHPUnit incompatibilities.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after NoBinaryStringFixer, NoUselessConcatOperatorFixer, PhpUnitDedicateAssertFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -16;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([PhpUnitTargetVersion::VERSION_7_5, PhpUnitTargetVersion::VERSION_NEWEST])
|
||||
->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$anonymousClassIndices = [];
|
||||
$tokenAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
if (!$tokens[$index]->isGivenKind(T_CLASS) || !$tokenAnalyzer->isAnonymousClass($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openingBraceIndex = $tokens->getNextTokenOfKind($index, ['{']);
|
||||
$closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $openingBraceIndex);
|
||||
|
||||
$anonymousClassIndices[$closingBraceIndex] = $openingBraceIndex;
|
||||
}
|
||||
|
||||
for ($index = $endIndex - 1; $index > $startIndex; --$index) {
|
||||
if (isset($anonymousClassIndices[$index])) {
|
||||
$index = $anonymousClassIndices[$index];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionName = strtolower($tokens[$index]->getContent());
|
||||
|
||||
if ('assertinternaltype' !== $functionName && 'assertnotinternaltype' !== $functionName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bracketTokenIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$bracketTokenIndex]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expectedTypeTokenIndex = $tokens->getNextMeaningfulToken($bracketTokenIndex);
|
||||
$expectedTypeToken = $tokens[$expectedTypeTokenIndex];
|
||||
|
||||
if (!$expectedTypeToken->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expectedType = trim($expectedTypeToken->getContent(), '\'"');
|
||||
|
||||
if (!isset($this->typeToDedicatedAssertMap[$expectedType])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$commaTokenIndex = $tokens->getNextMeaningfulToken($expectedTypeTokenIndex);
|
||||
|
||||
if (!$tokens[$commaTokenIndex]->equals(',')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newAssertion = $this->typeToDedicatedAssertMap[$expectedType];
|
||||
|
||||
if ('assertnotinternaltype' === $functionName) {
|
||||
$newAssertion = str_replace('Is', 'IsNot', $newAssertion);
|
||||
$newAssertion = str_replace('Null', 'NotNull', $newAssertion);
|
||||
}
|
||||
|
||||
$nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($commaTokenIndex);
|
||||
|
||||
$tokens->overrideRange($index, $nextMeaningfulTokenIndex - 1, [
|
||||
new Token([T_STRING, $newAssertion]),
|
||||
new Token('('),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitExpectationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $methodMap = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->methodMap = [
|
||||
'setExpectedException' => 'expectExceptionMessage',
|
||||
];
|
||||
|
||||
if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_6)) {
|
||||
$this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageRegExp';
|
||||
}
|
||||
|
||||
if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_8_4)) {
|
||||
$this->methodMap['setExpectedExceptionRegExp'] = 'expectExceptionMessageMatches';
|
||||
$this->methodMap['expectExceptionMessageRegExp'] = 'expectExceptionMessageMatches';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Usages of `->setExpectedException*` methods MUST be replaced by `->expectException*` methods.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testFoo()
|
||||
{
|
||||
$this->setExpectedException("RuntimeException", "Msg", 123);
|
||||
foo();
|
||||
}
|
||||
|
||||
public function testBar()
|
||||
{
|
||||
$this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testFoo()
|
||||
{
|
||||
$this->setExpectedException("RuntimeException", null, 123);
|
||||
foo();
|
||||
}
|
||||
|
||||
public function testBar()
|
||||
{
|
||||
$this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
',
|
||||
['target' => PhpUnitTargetVersion::VERSION_8_4]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testFoo()
|
||||
{
|
||||
$this->setExpectedException("RuntimeException", null, 123);
|
||||
foo();
|
||||
}
|
||||
|
||||
public function testBar()
|
||||
{
|
||||
$this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
',
|
||||
['target' => PhpUnitTargetVersion::VERSION_5_6]
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testFoo()
|
||||
{
|
||||
$this->setExpectedException("RuntimeException", "Msg", 123);
|
||||
foo();
|
||||
}
|
||||
|
||||
public function testBar()
|
||||
{
|
||||
$this->setExpectedExceptionRegExp("RuntimeException", "/Msg.*/", 123);
|
||||
bar();
|
||||
}
|
||||
}
|
||||
',
|
||||
['target' => PhpUnitTargetVersion::VERSION_5_2]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after PhpUnitNoExpectationAnnotationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([PhpUnitTargetVersion::VERSION_5_2, PhpUnitTargetVersion::VERSION_5_6, PhpUnitTargetVersion::VERSION_8_4, PhpUnitTargetVersion::VERSION_NEWEST])
|
||||
->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
foreach (Token::getObjectOperatorKinds() as $objectOperator) {
|
||||
$this->applyPhpUnitClassFixWithObjectOperator($tokens, $startIndex, $endIndex, $objectOperator);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyPhpUnitClassFixWithObjectOperator(Tokens $tokens, int $startIndex, int $endIndex, int $objectOperator): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
$oldMethodSequence = [
|
||||
[T_VARIABLE, '$this'],
|
||||
[$objectOperator],
|
||||
[T_STRING],
|
||||
];
|
||||
|
||||
for ($index = $startIndex; $startIndex < $endIndex; ++$index) {
|
||||
$match = $tokens->findSequence($oldMethodSequence, $index);
|
||||
|
||||
if (null === $match) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$thisIndex, , $index] = array_keys($match);
|
||||
|
||||
if (!isset($this->methodMap[$tokens[$index]->getContent()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openIndex = $tokens->getNextTokenOfKind($index, ['(']);
|
||||
$closeIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);
|
||||
$commaIndex = $tokens->getPrevMeaningfulToken($closeIndex);
|
||||
if ($tokens[$commaIndex]->equals(',')) {
|
||||
$tokens->removeTrailingWhitespace($commaIndex);
|
||||
$tokens->clearAt($commaIndex);
|
||||
}
|
||||
|
||||
$arguments = $argumentsAnalyzer->getArguments($tokens, $openIndex, $closeIndex);
|
||||
$argumentsCnt = \count($arguments);
|
||||
|
||||
$argumentsReplacements = ['expectException', $this->methodMap[$tokens[$index]->getContent()], 'expectExceptionCode'];
|
||||
|
||||
$indent = $this->whitespacesConfig->getLineEnding().WhitespacesAnalyzer::detectIndent($tokens, $thisIndex);
|
||||
|
||||
$isMultilineWhitespace = false;
|
||||
|
||||
for ($cnt = $argumentsCnt - 1; $cnt >= 1; --$cnt) {
|
||||
$argStart = array_keys($arguments)[$cnt];
|
||||
$argBefore = $tokens->getPrevMeaningfulToken($argStart);
|
||||
|
||||
if ('expectExceptionMessage' === $argumentsReplacements[$cnt]) {
|
||||
$paramIndicatorIndex = $tokens->getNextMeaningfulToken($argBefore);
|
||||
$afterParamIndicatorIndex = $tokens->getNextMeaningfulToken($paramIndicatorIndex);
|
||||
|
||||
if (
|
||||
$tokens[$paramIndicatorIndex]->equals([T_STRING, 'null'], false)
|
||||
&& $tokens[$afterParamIndicatorIndex]->equals(')')
|
||||
) {
|
||||
if ($tokens[$argBefore + 1]->isWhitespace()) {
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($argBefore + 1);
|
||||
}
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($argBefore);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($paramIndicatorIndex);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$isMultilineWhitespace = $isMultilineWhitespace || ($tokens[$argStart]->isWhitespace() && !$tokens[$argStart]->isWhitespace(" \t"));
|
||||
$tokensOverrideArgStart = [
|
||||
new Token([T_WHITESPACE, $indent]),
|
||||
new Token([T_VARIABLE, '$this']),
|
||||
new Token([T_OBJECT_OPERATOR, '->']),
|
||||
new Token([T_STRING, $argumentsReplacements[$cnt]]),
|
||||
new Token('('),
|
||||
];
|
||||
$tokensOverrideArgBefore = [
|
||||
new Token(')'),
|
||||
new Token(';'),
|
||||
];
|
||||
|
||||
if ($isMultilineWhitespace) {
|
||||
$tokensOverrideArgStart[] = new Token([T_WHITESPACE, $indent.$this->whitespacesConfig->getIndent()]);
|
||||
array_unshift($tokensOverrideArgBefore, new Token([T_WHITESPACE, $indent]));
|
||||
}
|
||||
|
||||
if ($tokens[$argStart]->isWhitespace()) {
|
||||
$tokens->overrideRange($argStart, $argStart, $tokensOverrideArgStart);
|
||||
} else {
|
||||
$tokens->insertAt($argStart, $tokensOverrideArgStart);
|
||||
}
|
||||
|
||||
$tokens->overrideRange($argBefore, $argBefore, $tokensOverrideArgBefore);
|
||||
}
|
||||
|
||||
$methodName = 'expectException';
|
||||
if ('expectExceptionMessageRegExp' === $tokens[$index]->getContent()) {
|
||||
$methodName = $this->methodMap[$tokens[$index]->getContent()];
|
||||
}
|
||||
$tokens[$index] = new Token([T_STRING, $methodName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Roland Franssen <franssen.roland@gmail.com>
|
||||
*/
|
||||
final class PhpUnitFqcnAnnotationFixer extends AbstractPhpUnitFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPUnit annotations should be a FQCNs including a root namespace.',
|
||||
[new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
* @covers Project\NameSpace\Something
|
||||
* @coversDefaultClass Project\Default
|
||||
* @uses Project\Test\Util
|
||||
*/
|
||||
public function testSomeTest()
|
||||
{
|
||||
}
|
||||
}
|
||||
'
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoUnusedImportsFixer, PhpdocOrderByValueFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -9;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$prevDocCommentIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_DOC_COMMENT]]);
|
||||
|
||||
if (null !== $prevDocCommentIndex) {
|
||||
$startIndex = $prevDocCommentIndex;
|
||||
}
|
||||
|
||||
$this->fixPhpUnitClass($tokens, $startIndex, $endIndex);
|
||||
}
|
||||
|
||||
private function fixPhpUnitClass(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
if ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
|
||||
$tokens[$index] = new Token([T_DOC_COMMENT, Preg::replace(
|
||||
'~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(?!(?:self|static)::)(\w.*)$~m',
|
||||
'$1\\\\$2',
|
||||
$tokens[$index]->getContent()
|
||||
)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\Line;
|
||||
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Gert de Pagter <BackEndTea@gmail.com>
|
||||
*/
|
||||
final class PhpUnitInternalClassFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'All PHPUnit test classes should be marked as internal.',
|
||||
[
|
||||
new CodeSample("<?php\nclass MyTest extends TestCase {}\n"),
|
||||
new CodeSample(
|
||||
"<?php\nclass MyTest extends TestCase {}\nfinal class FinalTest extends TestCase {}\nabstract class AbstractTest extends TestCase {}\n",
|
||||
['types' => ['final']]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before FinalInternalClassFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 68;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$types = ['normal', 'final', 'abstract'];
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('types', 'What types of classes to mark as internal'))
|
||||
->setAllowedValues([new AllowedValueSubset($types)])
|
||||
->setAllowedTypes(['array'])
|
||||
->setDefault(['normal', 'final'])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]);
|
||||
|
||||
if (!$this->isAllowedByConfiguration($tokens, $classIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex);
|
||||
|
||||
if ($this->isPHPDoc($tokens, $docBlockIndex)) {
|
||||
$this->updateDocBlockIfNeeded($tokens, $docBlockIndex);
|
||||
} else {
|
||||
$this->createDocBlock($tokens, $docBlockIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private function isAllowedByConfiguration(Tokens $tokens, int $i): bool
|
||||
{
|
||||
$typeIndex = $tokens->getPrevMeaningfulToken($i);
|
||||
if ($tokens[$typeIndex]->isGivenKind(T_FINAL)) {
|
||||
return \in_array('final', $this->configuration['types'], true);
|
||||
}
|
||||
|
||||
if ($tokens[$typeIndex]->isGivenKind(T_ABSTRACT)) {
|
||||
return \in_array('abstract', $this->configuration['types'], true);
|
||||
}
|
||||
|
||||
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));
|
||||
$toInsert = [
|
||||
new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @internal".$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 (!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;
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\Line;
|
||||
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
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;
|
||||
use PhpCsFixer\Utils;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class PhpUnitMethodCasingFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const CAMEL_CASE = 'camel_case';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const SNAKE_CASE = 'snake_case';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Enforce camel (or snake) case for PHPUnit test methods, following configuration.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class MyTest extends \\PhpUnit\\FrameWork\\TestCase
|
||||
{
|
||||
public function test_my_code() {}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
class MyTest extends \\PhpUnit\\FrameWork\\TestCase
|
||||
{
|
||||
public function testMyCode() {}
|
||||
}
|
||||
',
|
||||
['case' => self::SNAKE_CASE]
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run after PhpUnitTestAnnotationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('case', 'Apply camel or snake case to test methods'))
|
||||
->setAllowedValues([self::CAMEL_CASE, self::SNAKE_CASE])
|
||||
->setDefault(self::CAMEL_CASE)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($index = $endIndex - 1; $index > $startIndex; --$index) {
|
||||
if (!$this->isTestMethod($tokens, $index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$functionName = $tokens[$functionNameIndex]->getContent();
|
||||
$newFunctionName = $this->updateMethodCasing($functionName);
|
||||
|
||||
if ($newFunctionName !== $functionName) {
|
||||
$tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]);
|
||||
}
|
||||
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $index);
|
||||
|
||||
if ($this->isPHPDoc($tokens, $docBlockIndex)) {
|
||||
$this->updateDocBlock($tokens, $docBlockIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function updateMethodCasing(string $functionName): string
|
||||
{
|
||||
$parts = explode('::', $functionName);
|
||||
|
||||
$functionNamePart = array_pop($parts);
|
||||
|
||||
if (self::CAMEL_CASE === $this->configuration['case']) {
|
||||
$newFunctionNamePart = $functionNamePart;
|
||||
$newFunctionNamePart = ucwords($newFunctionNamePart, '_');
|
||||
$newFunctionNamePart = str_replace('_', '', $newFunctionNamePart);
|
||||
$newFunctionNamePart = lcfirst($newFunctionNamePart);
|
||||
} else {
|
||||
$newFunctionNamePart = Utils::camelCaseToUnderscore($functionNamePart);
|
||||
}
|
||||
|
||||
$parts[] = $newFunctionNamePart;
|
||||
|
||||
return implode('::', $parts);
|
||||
}
|
||||
|
||||
private function isTestMethod(Tokens $tokens, int $index): bool
|
||||
{
|
||||
// Check if we are dealing with a (non-abstract, non-lambda) function
|
||||
if (!$this->isMethod($tokens, $index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the function name starts with test it's a test
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$functionName = $tokens[$functionNameIndex]->getContent();
|
||||
|
||||
if (str_starts_with($functionName, 'test')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $index);
|
||||
|
||||
return
|
||||
$this->isPHPDoc($tokens, $docBlockIndex) // If the function doesn't have test in its name, and no doc block, it's not a test
|
||||
&& str_contains($tokens[$docBlockIndex]->getContent(), '@test')
|
||||
;
|
||||
}
|
||||
|
||||
private function isMethod(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index);
|
||||
}
|
||||
|
||||
private function updateDocBlock(Tokens $tokens, int $docBlockIndex): void
|
||||
{
|
||||
$doc = new DocBlock($tokens[$docBlockIndex]->getContent());
|
||||
$lines = $doc->getLines();
|
||||
|
||||
$docBlockNeedsUpdate = false;
|
||||
for ($inc = 0; $inc < \count($lines); ++$inc) {
|
||||
$lineContent = $lines[$inc]->getContent();
|
||||
if (!str_contains($lineContent, '@depends')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newLineContent = Preg::replaceCallback('/(@depends\s+)(.+)(\b)/', function (array $matches): string {
|
||||
return sprintf(
|
||||
'%s%s%s',
|
||||
$matches[1],
|
||||
$this->updateMethodCasing($matches[2]),
|
||||
$matches[3]
|
||||
);
|
||||
}, $lineContent);
|
||||
|
||||
if ($newLineContent !== $lineContent) {
|
||||
$lines[$inc] = new Line($newLineContent);
|
||||
$docBlockNeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($docBlockNeedsUpdate) {
|
||||
$lines = implode('', $lines);
|
||||
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitMockFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $fixCreatePartialMock;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Usages of `->getMock` and `->getMockWithoutInvokingTheOriginalConstructor` methods MUST be replaced by `->createMock` or `->createPartialMock` methods.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testFoo()
|
||||
{
|
||||
$mock = $this->getMockWithoutInvokingTheOriginalConstructor("Foo");
|
||||
$mock1 = $this->getMock("Foo");
|
||||
$mock1 = $this->getMock("Bar", ["aaa"]);
|
||||
$mock1 = $this->getMock("Baz", ["aaa"], ["argument"]); // version with more than 2 params is not supported
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testFoo()
|
||||
{
|
||||
$mock1 = $this->getMock("Foo");
|
||||
$mock1 = $this->getMock("Bar", ["aaa"]); // version with multiple params is not supported
|
||||
}
|
||||
}
|
||||
',
|
||||
['target' => PhpUnitTargetVersion::VERSION_5_4]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->fixCreatePartialMock = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_5);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
if (!$tokens[$index]->isObjectOperator()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$index = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if ($tokens[$index]->equals([T_STRING, 'getMockWithoutInvokingTheOriginalConstructor'], false)) {
|
||||
$tokens[$index] = new Token([T_STRING, 'createMock']);
|
||||
} elseif ($tokens[$index]->equals([T_STRING, 'getMock'], false)) {
|
||||
$openingParenthesis = $tokens->getNextMeaningfulToken($index);
|
||||
$closingParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesis);
|
||||
|
||||
$argumentsCount = $argumentsAnalyzer->countArguments($tokens, $openingParenthesis, $closingParenthesis);
|
||||
|
||||
if (1 === $argumentsCount) {
|
||||
$tokens[$index] = new Token([T_STRING, 'createMock']);
|
||||
} elseif (2 === $argumentsCount && true === $this->fixCreatePartialMock) {
|
||||
$tokens[$index] = new Token([T_STRING, 'createPartialMock']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([PhpUnitTargetVersion::VERSION_5_4, PhpUnitTargetVersion::VERSION_5_5, PhpUnitTargetVersion::VERSION_NEWEST])
|
||||
->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Michał Adamski <michal.adamski@gmail.com>
|
||||
* @author Kuba Werłos <werlos@gmail.com>
|
||||
*/
|
||||
final class PhpUnitMockShortWillReturnFixer extends AbstractPhpUnitFixer
|
||||
{
|
||||
private const RETURN_METHODS_MAP = [
|
||||
'returnargument' => 'willReturnArgument',
|
||||
'returncallback' => 'willReturnCallback',
|
||||
'returnself' => 'willReturnSelf',
|
||||
'returnvalue' => 'willReturn',
|
||||
'returnvaluemap' => 'willReturnMap',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Usage of PHPUnit\'s mock e.g. `->will($this->returnValue(..))` must be replaced by its shorter equivalent such as `->willReturn(...)`.',
|
||||
[
|
||||
new CodeSample('<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSomeTest()
|
||||
{
|
||||
$someMock = $this->createMock(Some::class);
|
||||
$someMock->method("some")->will($this->returnSelf());
|
||||
$someMock->method("some")->will($this->returnValue("example"));
|
||||
$someMock->method("some")->will($this->returnArgument(2));
|
||||
$someMock->method("some")->will($this->returnCallback("str_rot13"));
|
||||
$someMock->method("some")->will($this->returnValueMap(["a","b","c"]));
|
||||
}
|
||||
}
|
||||
'),
|
||||
],
|
||||
null,
|
||||
'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
if (!$tokens[$index]->isObjectOperator()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionToReplaceIndex = $tokens->getNextMeaningfulToken($index);
|
||||
if (!$tokens[$functionToReplaceIndex]->equals([T_STRING, 'will'], false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionToReplaceOpeningBraceIndex = $tokens->getNextMeaningfulToken($functionToReplaceIndex);
|
||||
|
||||
if (!$tokens[$functionToReplaceOpeningBraceIndex]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classReferenceIndex = $tokens->getNextMeaningfulToken($functionToReplaceOpeningBraceIndex);
|
||||
$objectOperatorIndex = $tokens->getNextMeaningfulToken($classReferenceIndex);
|
||||
$functionToRemoveIndex = $tokens->getNextMeaningfulToken($objectOperatorIndex);
|
||||
|
||||
if (!$functionsAnalyzer->isTheSameClassCall($tokens, $functionToRemoveIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\array_key_exists(strtolower($tokens[$functionToRemoveIndex]->getContent()), self::RETURN_METHODS_MAP)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openingBraceIndex = $tokens->getNextMeaningfulToken($functionToRemoveIndex);
|
||||
|
||||
if (!$tokens[$openingBraceIndex]->equals('(')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($openingBraceIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$closingBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingBraceIndex);
|
||||
|
||||
$tokens[$functionToReplaceIndex] = new Token([T_STRING, self::RETURN_METHODS_MAP[strtolower($tokens[$functionToRemoveIndex]->getContent())]]);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($classReferenceIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($objectOperatorIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($functionToRemoveIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($openingBraceIndex);
|
||||
$tokens->clearTokenAndMergeSurroundingWhitespace($closingBraceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitNamespacedFixer extends AbstractFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $originalClassRegEx;
|
||||
|
||||
/**
|
||||
* Class Mappings.
|
||||
*
|
||||
* * [original classname => new classname] Some classes which match the
|
||||
* original class regular expression do not have a same-compound name-
|
||||
* space class and need a dedicated translation table. This trans-
|
||||
* lation table is defined in @see configure.
|
||||
*
|
||||
* @var array|string[] Class Mappings
|
||||
*/
|
||||
private $classMap;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
$codeSample = '<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSomething()
|
||||
{
|
||||
PHPUnit_Framework_Assert::assertTrue(true);
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
return new FixerDefinition(
|
||||
'PHPUnit classes MUST be used in namespaced version, e.g. `\PHPUnit\Framework\TestCase` instead of `\PHPUnit_Framework_TestCase`.',
|
||||
[
|
||||
new CodeSample($codeSample),
|
||||
new CodeSample($codeSample, ['target' => PhpUnitTargetVersion::VERSION_4_8]),
|
||||
],
|
||||
"PHPUnit v6 has finally fully switched to namespaces.\n"
|
||||
."You could start preparing the upgrade by switching from non-namespaced TestCase to namespaced one.\n"
|
||||
.'Forward compatibility layer (`\PHPUnit\Framework\TestCase` class) was backported to PHPUnit v4.8.35 and PHPUnit v5.4.0.'."\n"
|
||||
.'Extended forward compatibility layer (`PHPUnit\Framework\Assert`, `PHPUnit\Framework\BaseTestListener`, `PHPUnit\Framework\TestListener` classes) was introduced in v5.7.0.'."\n",
|
||||
'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCandidate(Tokens $tokens): bool
|
||||
{
|
||||
return $tokens->isTokenKindFound(T_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_6_0)) {
|
||||
$this->originalClassRegEx = '/^PHPUnit_\w+$/i';
|
||||
// @noinspection ClassConstantCanBeUsedInspection
|
||||
$this->classMap = [
|
||||
'PHPUnit_Extensions_PhptTestCase' => 'PHPUnit\Runner\PhptTestCase',
|
||||
'PHPUnit_Framework_Constraint' => 'PHPUnit\Framework\Constraint\Constraint',
|
||||
'PHPUnit_Framework_Constraint_StringMatches' => 'PHPUnit\Framework\Constraint\StringMatchesFormatDescription',
|
||||
'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => 'PHPUnit\Framework\Constraint\JsonMatchesErrorMessageProvider',
|
||||
'PHPUnit_Framework_Constraint_PCREMatch' => 'PHPUnit\Framework\Constraint\RegularExpression',
|
||||
'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => 'PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression',
|
||||
'PHPUnit_Framework_Constraint_And' => 'PHPUnit\Framework\Constraint\LogicalAnd',
|
||||
'PHPUnit_Framework_Constraint_Or' => 'PHPUnit\Framework\Constraint\LogicalOr',
|
||||
'PHPUnit_Framework_Constraint_Not' => 'PHPUnit\Framework\Constraint\LogicalNot',
|
||||
'PHPUnit_Framework_Constraint_Xor' => 'PHPUnit\Framework\Constraint\LogicalXor',
|
||||
'PHPUnit_Framework_Error' => 'PHPUnit\Framework\Error\Error',
|
||||
'PHPUnit_Framework_TestSuite_DataProvider' => 'PHPUnit\Framework\DataProviderTestSuite',
|
||||
'PHPUnit_Framework_MockObject_Invocation_Static' => 'PHPUnit\Framework\MockObject\Invocation\StaticInvocation',
|
||||
'PHPUnit_Framework_MockObject_Invocation_Object' => 'PHPUnit\Framework\MockObject\Invocation\ObjectInvocation',
|
||||
'PHPUnit_Framework_MockObject_Stub_Return' => 'PHPUnit\Framework\MockObject\Stub\ReturnStub',
|
||||
'PHPUnit_Runner_Filter_Group_Exclude' => 'PHPUnit\Runner\Filter\ExcludeGroupFilterIterator',
|
||||
'PHPUnit_Runner_Filter_Group_Include' => 'PHPUnit\Runner\Filter\IncludeGroupFilterIterator',
|
||||
'PHPUnit_Runner_Filter_Test' => 'PHPUnit\Runner\Filter\NameFilterIterator',
|
||||
'PHPUnit_Util_PHP' => 'PHPUnit\Util\PHP\AbstractPhpProcess',
|
||||
'PHPUnit_Util_PHP_Default' => 'PHPUnit\Util\PHP\DefaultPhpProcess',
|
||||
'PHPUnit_Util_PHP_Windows' => 'PHPUnit\Util\PHP\WindowsPhpProcess',
|
||||
'PHPUnit_Util_Regex' => 'PHPUnit\Util\RegularExpression',
|
||||
'PHPUnit_Util_TestDox_ResultPrinter_XML' => 'PHPUnit\Util\TestDox\XmlResultPrinter',
|
||||
'PHPUnit_Util_TestDox_ResultPrinter_HTML' => 'PHPUnit\Util\TestDox\HtmlResultPrinter',
|
||||
'PHPUnit_Util_TestDox_ResultPrinter_Text' => 'PHPUnit\Util\TestDox\TextResultPrinter',
|
||||
'PHPUnit_Util_TestSuiteIterator' => 'PHPUnit\Framework\TestSuiteIterator',
|
||||
'PHPUnit_Util_XML' => 'PHPUnit\Util\Xml',
|
||||
];
|
||||
} elseif (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_5_7)) {
|
||||
$this->originalClassRegEx = '/^PHPUnit_Framework_TestCase|PHPUnit_Framework_Assert|PHPUnit_Framework_BaseTestListener|PHPUnit_Framework_TestListener$/i';
|
||||
$this->classMap = [];
|
||||
} else {
|
||||
$this->originalClassRegEx = '/^PHPUnit_Framework_TestCase$/i';
|
||||
$this->classMap = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
|
||||
{
|
||||
$importedOriginalClassesMap = [];
|
||||
$currIndex = 0;
|
||||
|
||||
while (true) {
|
||||
$currIndex = $tokens->getNextTokenOfKind($currIndex, [[T_STRING]]);
|
||||
|
||||
if (null === $currIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($currIndex);
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$originalClass = $tokens[$currIndex]->getContent();
|
||||
|
||||
if (1 !== Preg::match($this->originalClassRegEx, $originalClass)) {
|
||||
++$currIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$substituteTokens = $this->generateReplacement($originalClass);
|
||||
|
||||
$tokens->clearAt($currIndex);
|
||||
$tokens->insertAt(
|
||||
$currIndex,
|
||||
isset($importedOriginalClassesMap[$originalClass]) ? $substituteTokens[$substituteTokens->getSize() - 1] : $substituteTokens
|
||||
);
|
||||
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($currIndex);
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_USE)) {
|
||||
$importedOriginalClassesMap[$originalClass] = true;
|
||||
} elseif ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
|
||||
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
|
||||
|
||||
if ($tokens[$prevIndex]->isGivenKind(T_USE)) {
|
||||
$importedOriginalClassesMap[$originalClass] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([PhpUnitTargetVersion::VERSION_4_8, PhpUnitTargetVersion::VERSION_5_7, PhpUnitTargetVersion::VERSION_6_0, PhpUnitTargetVersion::VERSION_NEWEST])
|
||||
->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function generateReplacement(string $originalClassName): Tokens
|
||||
{
|
||||
$delimiter = '_';
|
||||
$string = $originalClassName;
|
||||
|
||||
if (isset($this->classMap[$originalClassName])) {
|
||||
$delimiter = '\\';
|
||||
$string = $this->classMap[$originalClassName];
|
||||
}
|
||||
|
||||
$parts = explode($delimiter, $string);
|
||||
$tokensArray = [];
|
||||
|
||||
while (!empty($parts)) {
|
||||
$tokensArray[] = new Token([T_STRING, array_shift($parts)]);
|
||||
if (!empty($parts)) {
|
||||
$tokensArray[] = new Token([T_NS_SEPARATOR, '\\']);
|
||||
}
|
||||
}
|
||||
|
||||
return Tokens::fromArray($tokensArray);
|
||||
}
|
||||
}
|
||||
Vendored
+283
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitNoExpectationAnnotationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $fixMessageRegExp;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $configuration): void
|
||||
{
|
||||
parent::configure($configuration);
|
||||
|
||||
$this->fixMessageRegExp = PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_4_3);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Usages of `@expectedException*` annotations MUST be replaced by `->setExpectedException*` methods.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException FooException
|
||||
* @expectedExceptionMessageRegExp /foo.*$/
|
||||
* @expectedExceptionCode 123
|
||||
*/
|
||||
function testAaa()
|
||||
{
|
||||
aaa();
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException FooException
|
||||
* @expectedExceptionCode 123
|
||||
*/
|
||||
function testBbb()
|
||||
{
|
||||
bbb();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException FooException
|
||||
* @expectedExceptionMessageRegExp /foo.*$/
|
||||
*/
|
||||
function testCcc()
|
||||
{
|
||||
ccc();
|
||||
}
|
||||
}
|
||||
',
|
||||
['target' => PhpUnitTargetVersion::VERSION_3_2]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when PHPUnit classes are overridden or not accessible, or when project has PHPUnit incompatibilities.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, PhpUnitExpectationFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('target', 'Target version of PHPUnit.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues([PhpUnitTargetVersion::VERSION_3_2, PhpUnitTargetVersion::VERSION_4_3, PhpUnitTargetVersion::VERSION_NEWEST])
|
||||
->setDefault(PhpUnitTargetVersion::VERSION_NEWEST)
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('use_class_const', 'Use ::class notation.'))
|
||||
->setAllowedTypes(['bool'])
|
||||
->setDefault(true)
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
if (!$tokens[$i]->isGivenKind(T_FUNCTION) || $tokensAnalyzer->isLambda($i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionIndex = $i;
|
||||
$docBlockIndex = $i;
|
||||
|
||||
// ignore abstract functions
|
||||
$braceIndex = $tokens->getNextTokenOfKind($functionIndex, [';', '{']);
|
||||
if (!$tokens[$braceIndex]->equals('{')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
do {
|
||||
$docBlockIndex = $tokens->getPrevNonWhitespace($docBlockIndex);
|
||||
} while ($tokens[$docBlockIndex]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_FINAL, T_ABSTRACT, T_COMMENT]));
|
||||
|
||||
if (!$tokens[$docBlockIndex]->isGivenKind(T_DOC_COMMENT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DocBlock($tokens[$docBlockIndex]->getContent());
|
||||
$annotations = [];
|
||||
|
||||
foreach ($doc->getAnnotationsOfType([
|
||||
'expectedException',
|
||||
'expectedExceptionCode',
|
||||
'expectedExceptionMessage',
|
||||
'expectedExceptionMessageRegExp',
|
||||
]) as $annotation) {
|
||||
$tag = $annotation->getTag()->getName();
|
||||
$content = $this->extractContentFromAnnotation($annotation);
|
||||
$annotations[$tag] = $content;
|
||||
$annotation->remove();
|
||||
}
|
||||
|
||||
if (!isset($annotations['expectedException'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->fixMessageRegExp && isset($annotations['expectedExceptionMessageRegExp'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
|
||||
|
||||
$paramList = $this->annotationsToParamList($annotations);
|
||||
|
||||
$newMethodsCode = '<?php $this->'
|
||||
.(isset($annotations['expectedExceptionMessageRegExp']) ? 'setExpectedExceptionRegExp' : 'setExpectedException')
|
||||
.'('
|
||||
.implode(', ', $paramList)
|
||||
.');';
|
||||
$newMethods = Tokens::fromCode($newMethodsCode);
|
||||
$newMethods[0] = new Token([
|
||||
T_WHITESPACE,
|
||||
$this->whitespacesConfig->getLineEnding().$originalIndent.$this->whitespacesConfig->getIndent(),
|
||||
]);
|
||||
|
||||
// apply changes
|
||||
$docContent = $doc->getContent();
|
||||
if ('' === $docContent) {
|
||||
$docContent = '/** */';
|
||||
}
|
||||
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $docContent]);
|
||||
$tokens->insertAt($braceIndex + 1, $newMethods);
|
||||
|
||||
$whitespaceIndex = $braceIndex + $newMethods->getSize() + 1;
|
||||
$tokens[$whitespaceIndex] = new Token([
|
||||
T_WHITESPACE,
|
||||
$this->whitespacesConfig->getLineEnding().$tokens[$whitespaceIndex]->getContent(),
|
||||
]);
|
||||
|
||||
$i = $docBlockIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractContentFromAnnotation(Annotation $annotation): string
|
||||
{
|
||||
$tag = $annotation->getTag()->getName();
|
||||
|
||||
if (1 !== Preg::match('/@'.$tag.'\s+(.+)$/s', $annotation->getContent(), $matches)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = Preg::replace('/\*+\/$/', '', $matches[1]);
|
||||
|
||||
if (Preg::match('/\R/u', $content)) {
|
||||
$content = Preg::replace('/\s*\R+\s*\*\s*/u', ' ', $content);
|
||||
}
|
||||
|
||||
return rtrim($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $annotations
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private function annotationsToParamList(array $annotations): array
|
||||
{
|
||||
$params = [];
|
||||
$exceptionClass = ltrim($annotations['expectedException'], '\\');
|
||||
|
||||
if (str_contains($exceptionClass, '*')) {
|
||||
$exceptionClass = substr($exceptionClass, 0, strpos($exceptionClass, '*'));
|
||||
}
|
||||
|
||||
$exceptionClass = trim($exceptionClass);
|
||||
|
||||
if (true === $this->configuration['use_class_const']) {
|
||||
$params[] = "\\{$exceptionClass}::class";
|
||||
} else {
|
||||
$params[] = "'{$exceptionClass}'";
|
||||
}
|
||||
|
||||
if (isset($annotations['expectedExceptionMessage'])) {
|
||||
$params[] = var_export($annotations['expectedExceptionMessage'], true);
|
||||
} elseif (isset($annotations['expectedExceptionMessageRegExp'])) {
|
||||
$params[] = var_export($annotations['expectedExceptionMessageRegExp'], true);
|
||||
} elseif (isset($annotations['expectedExceptionCode'])) {
|
||||
$params[] = 'null';
|
||||
}
|
||||
|
||||
if (isset($annotations['expectedExceptionCode'])) {
|
||||
$params[] = $annotations['expectedExceptionCode'];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Gert de Pagter
|
||||
*/
|
||||
final class PhpUnitSetUpTearDownVisibilityFixer extends AbstractPhpUnitFixer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Changes the visibility of the `setUp()` and `tearDown()` functions of PHPUnit to `protected`, to match the PHPUnit TestCase.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $hello;
|
||||
public function setUp()
|
||||
{
|
||||
$this->hello = "hello";
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
$this->hello = null;
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
],
|
||||
null,
|
||||
'This fixer may change functions named `setUp()` or `tearDown()` outside of PHPUnit tests, '.
|
||||
'when a class is wrongly seen as a PHPUnit test.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$counter = 0;
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
if (2 === $counter) {
|
||||
break; // we've seen both method we are interested in, so stop analyzing this class
|
||||
}
|
||||
|
||||
if (!$this->isSetupOrTearDownMethod($tokens, $i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++$counter;
|
||||
$visibility = $tokensAnalyzer->getMethodAttributes($i)['visibility'];
|
||||
|
||||
if (T_PUBLIC === $visibility) {
|
||||
$index = $tokens->getPrevTokenOfKind($i, [[T_PUBLIC]]);
|
||||
$tokens[$index] = new Token([T_PROTECTED, 'protected']);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $visibility) {
|
||||
$tokens->insertAt($i, [new Token([T_PROTECTED, 'protected']), new Token([T_WHITESPACE, ' '])]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isSetupOrTearDownMethod(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
$isMethod = $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index);
|
||||
if (!$isMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$functionName = strtolower($tokens[$functionNameIndex]->getContent());
|
||||
|
||||
return 'setup' === $functionName || 'teardown' === $functionName;
|
||||
}
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\DocBlock\Annotation;
|
||||
use PhpCsFixer\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\Line;
|
||||
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Jefersson Nathan <malukenho.dev@gmail.com>
|
||||
*/
|
||||
final class PhpUnitSizeClassFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface, ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'All PHPUnit test cases should have `@small`, `@medium` or `@large` annotation to enable run time limits.',
|
||||
[
|
||||
new CodeSample("<?php\nclass MyTest extends TestCase {}\n"),
|
||||
new CodeSample("<?php\nclass MyTest extends TestCase {}\n", ['group' => 'medium']),
|
||||
],
|
||||
'The special groups [small, medium, large] provides a way to identify tests that are taking long to be executed.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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'])
|
||||
->setDefault('small')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$classIndex = $tokens->getPrevTokenOfKind($startIndex, [[T_CLASS]]);
|
||||
|
||||
if ($this->isAbstractClass($tokens, $classIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $classIndex);
|
||||
|
||||
if ($this->isPHPDoc($tokens, $docBlockIndex)) {
|
||||
$this->updateDocBlockIfNeeded($tokens, $docBlockIndex);
|
||||
} else {
|
||||
$this->createDocBlock($tokens, $docBlockIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private function isAbstractClass(Tokens $tokens, int $i): bool
|
||||
{
|
||||
$typeIndex = $tokens->getPrevMeaningfulToken($i);
|
||||
|
||||
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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitStrictFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private static array $assertionMap = [
|
||||
'assertAttributeEquals' => 'assertAttributeSame',
|
||||
'assertAttributeNotEquals' => 'assertAttributeNotSame',
|
||||
'assertEquals' => 'assertSame',
|
||||
'assertNotEquals' => 'assertNotSame',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'PHPUnit methods like `assertSame` should be used instead of `assertEquals`.',
|
||||
[
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSomeTest()
|
||||
{
|
||||
$this->assertAttributeEquals(a(), b());
|
||||
$this->assertAttributeNotEquals(a(), b());
|
||||
$this->assertEquals(a(), b());
|
||||
$this->assertNotEquals(a(), b());
|
||||
}
|
||||
}
|
||||
'
|
||||
),
|
||||
new CodeSample(
|
||||
'<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSomeTest()
|
||||
{
|
||||
$this->assertAttributeEquals(a(), b());
|
||||
$this->assertAttributeNotEquals(a(), b());
|
||||
$this->assertEquals(a(), b());
|
||||
$this->assertNotEquals(a(), b());
|
||||
}
|
||||
}
|
||||
',
|
||||
['assertions' => ['assertEquals']]
|
||||
),
|
||||
],
|
||||
null,
|
||||
'Risky when any of the functions are overridden or when testing object equality.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$argumentsAnalyzer = new ArgumentsAnalyzer();
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
foreach ($this->configuration['assertions'] as $methodBefore) {
|
||||
$methodAfter = self::$assertionMap[$methodBefore];
|
||||
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
$methodIndex = $tokens->getNextTokenOfKind($index, [[T_STRING, $methodBefore]]);
|
||||
|
||||
if (null === $methodIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$functionsAnalyzer->isTheSameClassCall($tokens, $methodIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$openingParenthesisIndex = $tokens->getNextMeaningfulToken($methodIndex);
|
||||
$argumentsCount = $argumentsAnalyzer->countArguments(
|
||||
$tokens,
|
||||
$openingParenthesisIndex,
|
||||
$tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openingParenthesisIndex)
|
||||
);
|
||||
|
||||
if (2 === $argumentsCount || 3 === $argumentsCount) {
|
||||
$tokens[$methodIndex] = new Token([T_STRING, $methodAfter]);
|
||||
}
|
||||
|
||||
$index = $methodIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('assertions', 'List of assertion methods to fix.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([new AllowedValueSubset(array_keys(self::$assertionMap))])
|
||||
->setDefault([
|
||||
'assertAttributeEquals',
|
||||
'assertAttributeNotEquals',
|
||||
'assertEquals',
|
||||
'assertNotEquals',
|
||||
])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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 Composer\Semver\Comparator;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class PhpUnitTargetVersion
|
||||
{
|
||||
public const VERSION_3_0 = '3.0';
|
||||
public const VERSION_3_2 = '3.2';
|
||||
public const VERSION_3_5 = '3.5';
|
||||
public const VERSION_4_3 = '4.3';
|
||||
public const VERSION_4_8 = '4.8';
|
||||
public const VERSION_5_0 = '5.0';
|
||||
public const VERSION_5_2 = '5.2';
|
||||
public const VERSION_5_4 = '5.4';
|
||||
public const VERSION_5_5 = '5.5';
|
||||
public const VERSION_5_6 = '5.6';
|
||||
public const VERSION_5_7 = '5.7';
|
||||
public const VERSION_6_0 = '6.0';
|
||||
public const VERSION_7_5 = '7.5';
|
||||
public const VERSION_8_4 = '8.4';
|
||||
public const VERSION_NEWEST = 'newest';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function fulfills(string $candidate, string $target): bool
|
||||
{
|
||||
if (self::VERSION_NEWEST === $target) {
|
||||
throw new \LogicException(sprintf('Parameter `target` shall not be provided as "%s", determine proper target for tested PHPUnit feature instead.', self::VERSION_NEWEST));
|
||||
}
|
||||
|
||||
if (self::VERSION_NEWEST === $candidate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Comparator::greaterThanOrEqualTo($candidate, $target);
|
||||
}
|
||||
}
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\DocBlock\DocBlock;
|
||||
use PhpCsFixer\DocBlock\Line;
|
||||
use PhpCsFixer\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Preg;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
|
||||
/**
|
||||
* @author Gert de Pagter
|
||||
*/
|
||||
final class PhpUnitTestAnnotationFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
return new FixerDefinition(
|
||||
'Adds or removes @test annotations from tests, following configuration.',
|
||||
[
|
||||
new CodeSample('<?php
|
||||
class Test extends \\PhpUnit\\FrameWork\\TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function itDoesSomething() {} }'.$this->whitespacesConfig->getLineEnding()),
|
||||
new CodeSample('<?php
|
||||
class Test extends \\PhpUnit\\FrameWork\\TestCase
|
||||
{
|
||||
public function testItDoesSomething() {}}'.$this->whitespacesConfig->getLineEnding(), ['style' => 'annotation']),
|
||||
],
|
||||
null,
|
||||
'This fixer may change the name of your tests, and could cause incompatibility with'.
|
||||
' abstract classes or interfaces.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before NoEmptyPhpdocFixer, PhpUnitMethodCasingFixer, PhpdocTrimFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
if ('annotation' === $this->configuration['style']) {
|
||||
$this->applyTestAnnotation($tokens, $startIndex, $endIndex);
|
||||
} else {
|
||||
$this->applyTestPrefix($tokens, $startIndex, $endIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('style', 'Whether to use the @test annotation or not.'))
|
||||
->setAllowedValues(['prefix', 'annotation'])
|
||||
->setDefault('prefix')
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function applyTestAnnotation(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
if (!$this->isTestMethod($tokens, $i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($i);
|
||||
$functionName = $tokens[$functionNameIndex]->getContent();
|
||||
|
||||
if ($this->hasTestPrefix($functionName) && !$this->hasProperTestAnnotation($tokens, $i)) {
|
||||
$newFunctionName = $this->removeTestPrefix($functionName);
|
||||
$tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]);
|
||||
}
|
||||
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $i);
|
||||
|
||||
if ($this->isPHPDoc($tokens, $docBlockIndex)) {
|
||||
$lines = $this->updateDocBlock($tokens, $docBlockIndex);
|
||||
$lines = $this->addTestAnnotation($lines, $tokens, $docBlockIndex);
|
||||
$lines = implode('', $lines);
|
||||
|
||||
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
|
||||
} else {
|
||||
// Create a new docblock if it didn't have one before;
|
||||
$this->createDocBlock($tokens, $docBlockIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function applyTestPrefix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
for ($i = $endIndex - 1; $i > $startIndex; --$i) {
|
||||
// We explicitly check again if the function has a doc block to save some time.
|
||||
if (!$this->isTestMethod($tokens, $i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $i);
|
||||
|
||||
if (!$this->isPHPDoc($tokens, $docBlockIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines = $this->updateDocBlock($tokens, $docBlockIndex);
|
||||
$lines = implode('', $lines);
|
||||
$tokens[$docBlockIndex] = new Token([T_DOC_COMMENT, $lines]);
|
||||
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($i);
|
||||
$functionName = $tokens[$functionNameIndex]->getContent();
|
||||
|
||||
if ($this->hasTestPrefix($functionName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newFunctionName = $this->addTestPrefix($functionName);
|
||||
$tokens[$functionNameIndex] = new Token([T_STRING, $newFunctionName]);
|
||||
}
|
||||
}
|
||||
|
||||
private function isTestMethod(Tokens $tokens, int $index): bool
|
||||
{
|
||||
// Check if we are dealing with a (non-abstract, non-lambda) function
|
||||
if (!$this->isMethod($tokens, $index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the function name starts with test it is a test
|
||||
$functionNameIndex = $tokens->getNextMeaningfulToken($index);
|
||||
$functionName = $tokens[$functionNameIndex]->getContent();
|
||||
|
||||
if ($this->hasTestPrefix($functionName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $index);
|
||||
|
||||
// If the function doesn't have test in its name, and no doc block, it is not a test
|
||||
return
|
||||
$this->isPHPDoc($tokens, $docBlockIndex)
|
||||
&& str_contains($tokens[$docBlockIndex]->getContent(), '@test')
|
||||
;
|
||||
}
|
||||
|
||||
private function isMethod(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$tokensAnalyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
return $tokens[$index]->isGivenKind(T_FUNCTION) && !$tokensAnalyzer->isLambda($index);
|
||||
}
|
||||
|
||||
private function hasTestPrefix(string $functionName): bool
|
||||
{
|
||||
return str_starts_with($functionName, 'test');
|
||||
}
|
||||
|
||||
private function hasProperTestAnnotation(Tokens $tokens, int $index): bool
|
||||
{
|
||||
$docBlockIndex = $this->getDocBlockIndex($tokens, $index);
|
||||
$doc = $tokens[$docBlockIndex]->getContent();
|
||||
|
||||
return 1 === Preg::match('/\*\s+@test\b/', $doc);
|
||||
}
|
||||
|
||||
private function removeTestPrefix(string $functionName): string
|
||||
{
|
||||
$remainder = Preg::replace('/^test(?=[A-Z_])_?/', '', $functionName);
|
||||
|
||||
if ('' === $remainder) {
|
||||
return $functionName;
|
||||
}
|
||||
|
||||
return lcfirst($remainder);
|
||||
}
|
||||
|
||||
private function addTestPrefix(string $functionName): string
|
||||
{
|
||||
return 'test'.ucfirst($functionName);
|
||||
}
|
||||
|
||||
private function createDocBlock(Tokens $tokens, int $docBlockIndex): void
|
||||
{
|
||||
$lineEnd = $this->whitespacesConfig->getLineEnding();
|
||||
$originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $tokens->getNextNonWhitespace($docBlockIndex));
|
||||
$toInsert = [
|
||||
new Token([T_DOC_COMMENT, '/**'.$lineEnd."{$originalIndent} * @test".$lineEnd."{$originalIndent} */"]),
|
||||
new Token([T_WHITESPACE, $lineEnd.$originalIndent]),
|
||||
];
|
||||
$index = $tokens->getNextMeaningfulToken($docBlockIndex);
|
||||
$tokens->insertAt($index, $toInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Line[]
|
||||
*/
|
||||
private function updateDocBlock(Tokens $tokens, int $docBlockIndex): array
|
||||
{
|
||||
$doc = new DocBlock($tokens[$docBlockIndex]->getContent());
|
||||
$lines = $doc->getLines();
|
||||
|
||||
return $this->updateLines($lines, $tokens, $docBlockIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Line[] $lines
|
||||
*
|
||||
* @return Line[]
|
||||
*/
|
||||
private function updateLines(array $lines, Tokens $tokens, int $docBlockIndex): array
|
||||
{
|
||||
$needsAnnotation = 'annotation' === $this->configuration['style'];
|
||||
|
||||
$doc = new DocBlock($tokens[$docBlockIndex]->getContent());
|
||||
for ($i = 0; $i < \count($lines); ++$i) {
|
||||
// If we need to add test annotation and it is a single line comment we need to deal with that separately
|
||||
if ($needsAnnotation && ($lines[$i]->isTheStart() && $lines[$i]->isTheEnd())) {
|
||||
if (!$this->doesDocBlockContainTest($doc)) {
|
||||
$lines = $this->splitUpDocBlock($lines, $tokens, $docBlockIndex);
|
||||
|
||||
return $this->updateLines($lines, $tokens, $docBlockIndex);
|
||||
}
|
||||
// One we split it up, we run the function again, so we deal with other things in a proper way
|
||||
}
|
||||
|
||||
if (!$needsAnnotation
|
||||
&& str_contains($lines[$i]->getContent(), ' @test')
|
||||
&& !str_contains($lines[$i]->getContent(), '@testWith')
|
||||
&& !str_contains($lines[$i]->getContent(), '@testdox')
|
||||
) {
|
||||
// We remove @test from the doc block
|
||||
$lines[$i] = new Line(str_replace(' @test', '', $lines[$i]->getContent()));
|
||||
}
|
||||
// ignore the line if it isn't @depends
|
||||
if (!str_contains($lines[$i]->getContent(), '@depends')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[$i] = $this->updateDependsAnnotation($lines[$i]);
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$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
|
||||
*
|
||||
* @param Line[] $lines
|
||||
*/
|
||||
private function getSingleLineDocBlockEntry(array $lines): string
|
||||
{
|
||||
$line = $lines[0];
|
||||
$line = str_replace('*/', '', $line->getContent());
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the depends tag on the current doc block.
|
||||
*/
|
||||
private function updateDependsAnnotation(Line $line): Line
|
||||
{
|
||||
if ('annotation' === $this->configuration['style']) {
|
||||
return $this->removeTestPrefixFromDependsAnnotation($line);
|
||||
}
|
||||
|
||||
return $this->addTestPrefixToDependsAnnotation($line);
|
||||
}
|
||||
|
||||
private function removeTestPrefixFromDependsAnnotation(Line $line): Line
|
||||
{
|
||||
$line = str_split($line->getContent());
|
||||
|
||||
$dependsIndex = $this->findWhereDependsFunctionNameStarts($line);
|
||||
$dependsFunctionName = implode('', \array_slice($line, $dependsIndex));
|
||||
|
||||
if ($this->hasTestPrefix($dependsFunctionName)) {
|
||||
$dependsFunctionName = $this->removeTestPrefix($dependsFunctionName);
|
||||
}
|
||||
array_splice($line, $dependsIndex);
|
||||
|
||||
return new Line(implode('', $line).$dependsFunctionName);
|
||||
}
|
||||
|
||||
private function addTestPrefixToDependsAnnotation(Line $line): Line
|
||||
{
|
||||
$line = str_split($line->getContent());
|
||||
$dependsIndex = $this->findWhereDependsFunctionNameStarts($line);
|
||||
$dependsFunctionName = implode('', \array_slice($line, $dependsIndex));
|
||||
|
||||
if (!$this->hasTestPrefix($dependsFunctionName)) {
|
||||
$dependsFunctionName = $this->addTestPrefix($dependsFunctionName);
|
||||
}
|
||||
|
||||
array_splice($line, $dependsIndex);
|
||||
|
||||
return new Line(implode('', $line).$dependsFunctionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps to find where the function name in the doc block starts.
|
||||
*
|
||||
* @param list<string> $line
|
||||
*/
|
||||
private function findWhereDependsFunctionNameStarts(array $line): int
|
||||
{
|
||||
$counter = \count($line);
|
||||
|
||||
do {
|
||||
--$counter;
|
||||
} while (' ' !== $line[$counter]);
|
||||
|
||||
return $counter + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Line[] $lines
|
||||
*
|
||||
* @return Line[]
|
||||
*/
|
||||
private function addTestAnnotation(array $lines, Tokens $tokens, int $docBlockIndex): array
|
||||
{
|
||||
$doc = new DocBlock($tokens[$docBlockIndex]->getContent());
|
||||
|
||||
if (!$this->doesDocBlockContainTest($doc)) {
|
||||
$originalIndent = WhitespacesAnalyzer::detectIndent($tokens, $docBlockIndex);
|
||||
$lineEnd = $this->whitespacesConfig->getLineEnding();
|
||||
|
||||
array_splice($lines, -1, 0, $originalIndent.' *'.$lineEnd.$originalIndent.' * @test'.$lineEnd);
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function doesDocBlockContainTest(DocBlock $doc): bool
|
||||
{
|
||||
return 0 !== \count($doc->getAnnotationsOfType('test'));
|
||||
}
|
||||
}
|
||||
www-api/vendor/friendsofphp/php-cs-fixer/src/Fixer/PhpUnit/PhpUnitTestCaseStaticMethodCallsFixer.php
Vendored
+491
@@ -0,0 +1,491 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\Fixer\AbstractPhpUnitFixer;
|
||||
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
|
||||
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
|
||||
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
|
||||
use PhpCsFixer\FixerDefinition\CodeSample;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinition;
|
||||
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
|
||||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
|
||||
use PhpCsFixer\Tokenizer\CT;
|
||||
use PhpCsFixer\Tokenizer\Token;
|
||||
use PhpCsFixer\Tokenizer\Tokens;
|
||||
use PhpCsFixer\Tokenizer\TokensAnalyzer;
|
||||
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
|
||||
|
||||
/**
|
||||
* @author Filippo Tessarotto <zoeslam@gmail.com>
|
||||
*/
|
||||
final class PhpUnitTestCaseStaticMethodCallsFixer extends AbstractPhpUnitFixer implements ConfigurableFixerInterface
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const CALL_TYPE_THIS = 'this';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const CALL_TYPE_SELF = 'self';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public const CALL_TYPE_STATIC = 'static';
|
||||
|
||||
/**
|
||||
* @var array<string,bool>
|
||||
*/
|
||||
private array $allowedValues = [
|
||||
self::CALL_TYPE_THIS => true,
|
||||
self::CALL_TYPE_SELF => true,
|
||||
self::CALL_TYPE_STATIC => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string,true>
|
||||
*/
|
||||
private array $staticMethods = [
|
||||
// Assert methods
|
||||
'anything' => true,
|
||||
'arrayHasKey' => true,
|
||||
'assertArrayHasKey' => true,
|
||||
'assertArrayNotHasKey' => true,
|
||||
'assertArraySubset' => true,
|
||||
'assertAttributeContains' => true,
|
||||
'assertAttributeContainsOnly' => true,
|
||||
'assertAttributeCount' => true,
|
||||
'assertAttributeEmpty' => true,
|
||||
'assertAttributeEquals' => true,
|
||||
'assertAttributeGreaterThan' => true,
|
||||
'assertAttributeGreaterThanOrEqual' => true,
|
||||
'assertAttributeInstanceOf' => true,
|
||||
'assertAttributeInternalType' => true,
|
||||
'assertAttributeLessThan' => true,
|
||||
'assertAttributeLessThanOrEqual' => true,
|
||||
'assertAttributeNotContains' => true,
|
||||
'assertAttributeNotContainsOnly' => true,
|
||||
'assertAttributeNotCount' => true,
|
||||
'assertAttributeNotEmpty' => true,
|
||||
'assertAttributeNotEquals' => true,
|
||||
'assertAttributeNotInstanceOf' => true,
|
||||
'assertAttributeNotInternalType' => true,
|
||||
'assertAttributeNotSame' => true,
|
||||
'assertAttributeSame' => true,
|
||||
'assertClassHasAttribute' => true,
|
||||
'assertClassHasStaticAttribute' => true,
|
||||
'assertClassNotHasAttribute' => true,
|
||||
'assertClassNotHasStaticAttribute' => true,
|
||||
'assertContains' => true,
|
||||
'assertContainsEquals' => true,
|
||||
'assertContainsOnly' => true,
|
||||
'assertContainsOnlyInstancesOf' => true,
|
||||
'assertCount' => true,
|
||||
'assertDirectoryDoesNotExist' => true,
|
||||
'assertDirectoryExists' => true,
|
||||
'assertDirectoryIsNotReadable' => true,
|
||||
'assertDirectoryIsNotWritable' => true,
|
||||
'assertDirectoryIsReadable' => true,
|
||||
'assertDirectoryIsWritable' => true,
|
||||
'assertDirectoryNotExists' => true,
|
||||
'assertDirectoryNotIsReadable' => true,
|
||||
'assertDirectoryNotIsWritable' => true,
|
||||
'assertDoesNotMatchRegularExpression' => true,
|
||||
'assertEmpty' => true,
|
||||
'assertEqualXMLStructure' => true,
|
||||
'assertEquals' => true,
|
||||
'assertEqualsCanonicalizing' => true,
|
||||
'assertEqualsIgnoringCase' => true,
|
||||
'assertEqualsWithDelta' => true,
|
||||
'assertFalse' => true,
|
||||
'assertFileDoesNotExist' => true,
|
||||
'assertFileEquals' => true,
|
||||
'assertFileEqualsCanonicalizing' => true,
|
||||
'assertFileEqualsIgnoringCase' => true,
|
||||
'assertFileExists' => true,
|
||||
'assertFileIsNotReadable' => true,
|
||||
'assertFileIsNotWritable' => true,
|
||||
'assertFileIsReadable' => true,
|
||||
'assertFileIsWritable' => true,
|
||||
'assertFileNotEquals' => true,
|
||||
'assertFileNotEqualsCanonicalizing' => true,
|
||||
'assertFileNotEqualsIgnoringCase' => true,
|
||||
'assertFileNotExists' => true,
|
||||
'assertFileNotIsReadable' => true,
|
||||
'assertFileNotIsWritable' => true,
|
||||
'assertFinite' => true,
|
||||
'assertGreaterThan' => true,
|
||||
'assertGreaterThanOrEqual' => true,
|
||||
'assertInfinite' => true,
|
||||
'assertInstanceOf' => true,
|
||||
'assertInternalType' => true,
|
||||
'assertIsArray' => true,
|
||||
'assertIsBool' => true,
|
||||
'assertIsCallable' => true,
|
||||
'assertIsClosedResource' => true,
|
||||
'assertIsFloat' => true,
|
||||
'assertIsInt' => true,
|
||||
'assertIsIterable' => true,
|
||||
'assertIsNotArray' => true,
|
||||
'assertIsNotBool' => true,
|
||||
'assertIsNotCallable' => true,
|
||||
'assertIsNotClosedResource' => true,
|
||||
'assertIsNotFloat' => true,
|
||||
'assertIsNotInt' => true,
|
||||
'assertIsNotIterable' => true,
|
||||
'assertIsNotNumeric' => true,
|
||||
'assertIsNotObject' => true,
|
||||
'assertIsNotReadable' => true,
|
||||
'assertIsNotResource' => true,
|
||||
'assertIsNotScalar' => true,
|
||||
'assertIsNotString' => true,
|
||||
'assertIsNotWritable' => true,
|
||||
'assertIsNumeric' => true,
|
||||
'assertIsObject' => true,
|
||||
'assertIsReadable' => true,
|
||||
'assertIsResource' => true,
|
||||
'assertIsScalar' => true,
|
||||
'assertIsString' => true,
|
||||
'assertIsWritable' => true,
|
||||
'assertJson' => true,
|
||||
'assertJsonFileEqualsJsonFile' => true,
|
||||
'assertJsonFileNotEqualsJsonFile' => true,
|
||||
'assertJsonStringEqualsJsonFile' => true,
|
||||
'assertJsonStringEqualsJsonString' => true,
|
||||
'assertJsonStringNotEqualsJsonFile' => true,
|
||||
'assertJsonStringNotEqualsJsonString' => true,
|
||||
'assertLessThan' => true,
|
||||
'assertLessThanOrEqual' => true,
|
||||
'assertMatchesRegularExpression' => true,
|
||||
'assertNan' => true,
|
||||
'assertNotContains' => true,
|
||||
'assertNotContainsEquals' => true,
|
||||
'assertNotContainsOnly' => true,
|
||||
'assertNotCount' => true,
|
||||
'assertNotEmpty' => true,
|
||||
'assertNotEquals' => true,
|
||||
'assertNotEqualsCanonicalizing' => true,
|
||||
'assertNotEqualsIgnoringCase' => true,
|
||||
'assertNotEqualsWithDelta' => true,
|
||||
'assertNotFalse' => true,
|
||||
'assertNotInstanceOf' => true,
|
||||
'assertNotInternalType' => true,
|
||||
'assertNotIsReadable' => true,
|
||||
'assertNotIsWritable' => true,
|
||||
'assertNotNull' => true,
|
||||
'assertNotRegExp' => true,
|
||||
'assertNotSame' => true,
|
||||
'assertNotSameSize' => true,
|
||||
'assertNotTrue' => true,
|
||||
'assertNull' => true,
|
||||
'assertObjectEquals' => true,
|
||||
'assertObjectHasAttribute' => true,
|
||||
'assertObjectNotHasAttribute' => true,
|
||||
'assertRegExp' => true,
|
||||
'assertSame' => true,
|
||||
'assertSameSize' => true,
|
||||
'assertStringContainsString' => true,
|
||||
'assertStringContainsStringIgnoringCase' => true,
|
||||
'assertStringEndsNotWith' => true,
|
||||
'assertStringEndsWith' => true,
|
||||
'assertStringEqualsFile' => true,
|
||||
'assertStringEqualsFileCanonicalizing' => true,
|
||||
'assertStringEqualsFileIgnoringCase' => true,
|
||||
'assertStringMatchesFormat' => true,
|
||||
'assertStringMatchesFormatFile' => true,
|
||||
'assertStringNotContainsString' => true,
|
||||
'assertStringNotContainsStringIgnoringCase' => true,
|
||||
'assertStringNotEqualsFile' => true,
|
||||
'assertStringNotEqualsFileCanonicalizing' => true,
|
||||
'assertStringNotEqualsFileIgnoringCase' => true,
|
||||
'assertStringNotMatchesFormat' => true,
|
||||
'assertStringNotMatchesFormatFile' => true,
|
||||
'assertStringStartsNotWith' => true,
|
||||
'assertStringStartsWith' => true,
|
||||
'assertThat' => true,
|
||||
'assertTrue' => true,
|
||||
'assertXmlFileEqualsXmlFile' => true,
|
||||
'assertXmlFileNotEqualsXmlFile' => true,
|
||||
'assertXmlStringEqualsXmlFile' => true,
|
||||
'assertXmlStringEqualsXmlString' => true,
|
||||
'assertXmlStringNotEqualsXmlFile' => true,
|
||||
'assertXmlStringNotEqualsXmlString' => true,
|
||||
'attribute' => true,
|
||||
'attributeEqualTo' => true,
|
||||
'callback' => true,
|
||||
'classHasAttribute' => true,
|
||||
'classHasStaticAttribute' => true,
|
||||
'contains' => true,
|
||||
'containsEqual' => true,
|
||||
'containsIdentical' => true,
|
||||
'containsOnly' => true,
|
||||
'containsOnlyInstancesOf' => true,
|
||||
'countOf' => true,
|
||||
'directoryExists' => true,
|
||||
'equalTo' => true,
|
||||
'equalToCanonicalizing' => true,
|
||||
'equalToIgnoringCase' => true,
|
||||
'equalToWithDelta' => true,
|
||||
'fail' => true,
|
||||
'fileExists' => true,
|
||||
'getCount' => true,
|
||||
'getObjectAttribute' => true,
|
||||
'getStaticAttribute' => true,
|
||||
'greaterThan' => true,
|
||||
'greaterThanOrEqual' => true,
|
||||
'identicalTo' => true,
|
||||
'isEmpty' => true,
|
||||
'isFalse' => true,
|
||||
'isFinite' => true,
|
||||
'isInfinite' => true,
|
||||
'isInstanceOf' => true,
|
||||
'isJson' => true,
|
||||
'isNan' => true,
|
||||
'isNull' => true,
|
||||
'isReadable' => true,
|
||||
'isTrue' => true,
|
||||
'isType' => true,
|
||||
'isWritable' => true,
|
||||
'lessThan' => true,
|
||||
'lessThanOrEqual' => true,
|
||||
'logicalAnd' => true,
|
||||
'logicalNot' => true,
|
||||
'logicalOr' => true,
|
||||
'logicalXor' => true,
|
||||
'markTestIncomplete' => true,
|
||||
'markTestSkipped' => true,
|
||||
'matches' => true,
|
||||
'matchesRegularExpression' => true,
|
||||
'objectEquals' => true,
|
||||
'objectHasAttribute' => true,
|
||||
'readAttribute' => true,
|
||||
'resetCount' => true,
|
||||
'stringContains' => true,
|
||||
'stringEndsWith' => true,
|
||||
'stringStartsWith' => true,
|
||||
|
||||
// TestCase methods
|
||||
'any' => true,
|
||||
'at' => true,
|
||||
'atLeast' => true,
|
||||
'atLeastOnce' => true,
|
||||
'atMost' => true,
|
||||
'exactly' => true,
|
||||
'never' => true,
|
||||
'once' => true,
|
||||
'onConsecutiveCalls' => true,
|
||||
'returnArgument' => true,
|
||||
'returnCallback' => true,
|
||||
'returnSelf' => true,
|
||||
'returnValue' => true,
|
||||
'returnValueMap' => true,
|
||||
'setUpBeforeClass' => true,
|
||||
'tearDownAfterClass' => true,
|
||||
'throwException' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, list<list<int|string>>>
|
||||
*/
|
||||
private array $conversionMap = [
|
||||
self::CALL_TYPE_THIS => [[T_OBJECT_OPERATOR, '->'], [T_VARIABLE, '$this']],
|
||||
self::CALL_TYPE_SELF => [[T_DOUBLE_COLON, '::'], [T_STRING, 'self']],
|
||||
self::CALL_TYPE_STATIC => [[T_DOUBLE_COLON, '::'], [T_STATIC, 'static']],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
$codeSample = '<?php
|
||||
final class MyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testMe()
|
||||
{
|
||||
$this->assertSame(1, 2);
|
||||
self::assertSame(1, 2);
|
||||
static::assertSame(1, 2);
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
return new FixerDefinition(
|
||||
'Calls to `PHPUnit\Framework\TestCase` static methods must all be of the same type, either `$this->`, `self::` or `static::`.',
|
||||
[
|
||||
new CodeSample($codeSample),
|
||||
new CodeSample($codeSample, ['call_type' => self::CALL_TYPE_THIS]),
|
||||
],
|
||||
null,
|
||||
'Risky when PHPUnit methods are overridden or not accessible, or when project has PHPUnit incompatibilities.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Must run before SelfStaticAccessorFixer.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isRisky(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
|
||||
{
|
||||
$thisFixer = $this;
|
||||
|
||||
return new FixerConfigurationResolver([
|
||||
(new FixerOptionBuilder('call_type', 'The call type to use for referring to PHPUnit methods.'))
|
||||
->setAllowedTypes(['string'])
|
||||
->setAllowedValues(array_keys($this->allowedValues))
|
||||
->setDefault('static')
|
||||
->getOption(),
|
||||
(new FixerOptionBuilder('methods', 'Dictionary of `method` => `call_type` values that differ from the default strategy.'))
|
||||
->setAllowedTypes(['array'])
|
||||
->setAllowedValues([static function (array $option) use ($thisFixer): bool {
|
||||
foreach ($option as $method => $value) {
|
||||
if (!isset($thisFixer->staticMethods[$method])) {
|
||||
throw new InvalidOptionsException(
|
||||
sprintf(
|
||||
'Unexpected "methods" key, expected any of "%s", got "%s".',
|
||||
implode('", "', array_keys($thisFixer->staticMethods)),
|
||||
\gettype($method).'#'.$method
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($thisFixer->allowedValues[$value])) {
|
||||
throw new InvalidOptionsException(
|
||||
sprintf(
|
||||
'Unexpected value for method "%s", expected any of "%s", got "%s".',
|
||||
$method,
|
||||
implode('", "', array_keys($thisFixer->allowedValues)),
|
||||
\is_object($value) ? \get_class($value) : (null === $value ? 'null' : \gettype($value).'#'.$value)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}])
|
||||
->setDefault([])
|
||||
->getOption(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$analyzer = new TokensAnalyzer($tokens);
|
||||
|
||||
for ($index = $startIndex; $index < $endIndex; ++$index) {
|
||||
// skip anonymous classes
|
||||
if ($tokens[$index]->isGivenKind(T_CLASS)) {
|
||||
$index = $this->findEndOfNextBlock($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$callType = $this->configuration['call_type'];
|
||||
|
||||
if ($tokens[$index]->isGivenKind(T_FUNCTION)) {
|
||||
// skip lambda
|
||||
if ($analyzer->isLambda($index)) {
|
||||
$index = $this->findEndOfNextBlock($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// do not change `self` to `this` in static methods
|
||||
if ('this' === $callType) {
|
||||
$attributes = $analyzer->getMethodAttributes($index);
|
||||
|
||||
if (false !== $attributes['static']) {
|
||||
$index = $this->findEndOfNextBlock($tokens, $index);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$tokens[$index]->isGivenKind(T_STRING) || !isset($this->staticMethods[$tokens[$index]->getContent()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextIndex = $tokens->getNextMeaningfulToken($index);
|
||||
|
||||
if (!$tokens[$nextIndex]->equals('(')) {
|
||||
$index = $nextIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tokens[$tokens->getNextMeaningfulToken($nextIndex)]->isGivenKind(CT::T_FIRST_CLASS_CALLABLE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$methodName = $tokens[$index]->getContent();
|
||||
|
||||
if (isset($this->configuration['methods'][$methodName])) {
|
||||
$callType = $this->configuration['methods'][$methodName];
|
||||
}
|
||||
|
||||
$operatorIndex = $tokens->getPrevMeaningfulToken($index);
|
||||
$referenceIndex = $tokens->getPrevMeaningfulToken($operatorIndex);
|
||||
|
||||
if (!$this->needsConversion($tokens, $index, $referenceIndex, $callType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$operatorIndex] = new Token($this->conversionMap[$callType][0]);
|
||||
$tokens[$referenceIndex] = new Token($this->conversionMap[$callType][1]);
|
||||
}
|
||||
}
|
||||
|
||||
private function needsConversion(Tokens $tokens, int $index, int $referenceIndex, string $callType): bool
|
||||
{
|
||||
$functionsAnalyzer = new FunctionsAnalyzer();
|
||||
|
||||
return $functionsAnalyzer->isTheSameClassCall($tokens, $index)
|
||||
&& !$tokens[$referenceIndex]->equals($this->conversionMap[$callType][1], false);
|
||||
}
|
||||
|
||||
private function findEndOfNextBlock(Tokens $tokens, int $index): int
|
||||
{
|
||||
$nextIndex = $tokens->getNextTokenOfKind($index, [';', '{']);
|
||||
|
||||
return $tokens[$nextIndex]->equals('{')
|
||||
? $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $nextIndex)
|
||||
: $nextIndex;
|
||||
}
|
||||
}
|
||||
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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\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;
|
||||
|
||||
/**
|
||||
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||
*/
|
||||
final class PhpUnitTestClassRequiresCoversFixer extends AbstractPhpUnitFixer implements WhitespacesAwareFixerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinition(): FixerDefinitionInterface
|
||||
{
|
||||
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}
|
||||
*/
|
||||
protected function applyPhpUnitClassFix(Tokens $tokens, int $startIndex, int $endIndex): void
|
||||
{
|
||||
$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);
|
||||
|
||||
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([
|
||||
'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)]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user