Skip to content

Commit

Permalink
minor #6029 PhpUnitDedicateAssertFixer - add "assertStringContainsStr…
Browse files Browse the repository at this point in the history
…ing" and "as… (SpacePossum)

This PR was merged into the master branch.

Discussion
----------

PhpUnitDedicateAssertFixer - add "assertStringContainsString" and "as…

…sertStringStartsWith" support

closes #4146

Commits
-------

7ed8125 PhpUnitDedicateAssertFixer - add "assertStringContainsString" and "assertStringEnds/StartsWith" support
  • Loading branch information
SpacePossum committed Sep 26, 2021
2 parents 0089c12 + 7ed8125 commit 7c09cca
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 100 deletions.
2 changes: 1 addition & 1 deletion src/Fixer/Alias/ModernizeStrposFixer.php
Expand Up @@ -77,7 +77,7 @@ public function getDefinition(): FixerDefinitionInterface
/**
* {@inheritdoc}
*
* Must run before BinaryOperatorSpacesFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NotOperatorWithSpaceFixer, NotOperatorWithSuccessorSpaceFixer, SingleSpaceAfterConstructFixer.
* Must run before BinaryOperatorSpacesFixer, NoExtraBlankLinesFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NotOperatorWithSpaceFixer, NotOperatorWithSuccessorSpaceFixer, PhpUnitDedicateAssertFixer, SingleSpaceAfterConstructFixer.
*/
public function getPriority(): int
{
Expand Down
179 changes: 152 additions & 27 deletions src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -35,29 +36,75 @@ final class PhpUnitDedicateAssertFixer extends AbstractPhpUnitFixer implements C
* @var array<string,array|true>
*/
private static $fixMap = [
'array_key_exists' => ['assertArrayNotHasKey', 'assertArrayHasKey'],
'empty' => ['assertNotEmpty', 'assertEmpty'],
'file_exists' => ['assertFileNotExists', 'assertFileExists'],
'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' => ['assertDirectoryNotExists', 'assertDirectoryExists'],
'is_dir' => [
'positive' => 'assertDirectoryExists',
'negative' => 'assertDirectoryNotExists',
],
'is_double' => true,
'is_float' => true,
'is_infinite' => ['assertFinite', 'assertInfinite'],
'is_infinite' => [
'positive' => 'assertInfinite',
'negative' => 'assertFinite',
],
'is_int' => true,
'is_integer' => true,
'is_long' => true,
'is_nan' => [false, 'assertNan'],
'is_null' => ['assertNotNull', 'assertNull'],
'is_nan' => [
'positive' => 'assertNan',
'negative' => false,
],
'is_null' => [
'positive' => 'assertNull',
'negative' => 'assertNotNull',
],
'is_numeric' => true,
'is_object' => true,
'is_readable' => ['assertNotIsReadable', 'assertIsReadable'],
'is_readable' => [
'positive' => 'assertIsReadable',
'negative' => 'assertNotIsReadable',
],
'is_real' => true,
'is_resource' => true,
'is_scalar' => true,
'is_string' => true,
'is_writable' => ['assertNotIsWritable', 'assertIsWritable'],
'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,
],
];

/**
Expand All @@ -77,6 +124,8 @@ public function configure(array $configuration): void
'array_key_exists',
'file_exists',
'is_null',
'str_ends_with',
'str_starts_with',
];

if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_3_5)) {
Expand Down Expand Up @@ -117,6 +166,12 @@ public function configure(array $configuration): void
'is_writable',
]);
}

if (PhpUnitTargetVersion::fulfills($this->configuration['target'], PhpUnitTargetVersion::VERSION_7_5)) {
$this->functions = array_merge($this->functions, [
'str_contains',
]);
}
}

/**
Expand Down Expand Up @@ -171,7 +226,7 @@ public function testSomeTest()
* {@inheritdoc}
*
* Must run before PhpUnitDedicateAssertInternalTypeFixer.
* Must run after NoAliasFunctionsFixer, PhpUnitConstructFixer.
* Must run after ModernizeStrposFixer, NoAliasFunctionsFixer, PhpUnitConstructFixer.
*/
public function getPriority(): int
{
Expand All @@ -183,10 +238,12 @@ public function getPriority(): int
*/
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, $assertCall);
$this->fixAssertTrueFalse($tokens, $argumentsAnalyzer, $assertCall);

continue;
}
Expand Down Expand Up @@ -224,9 +281,9 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn
]);
}

private function fixAssertTrueFalse(Tokens $tokens, array $assertCall): void
private function fixAssertTrueFalse(Tokens $tokens, ArgumentsAnalyzer $argumentsAnalyzer, array $assertCall): void
{
$testDefaultNamespaceTokenIndex = false;
$testDefaultNamespaceTokenIndex = null;
$testIndex = $tokens->getNextMeaningfulToken($assertCall['openBraceIndex']);

if (!$tokens[$testIndex]->isGivenKind([T_EMPTY, T_STRING])) {
Expand All @@ -239,30 +296,55 @@ private function fixAssertTrueFalse(Tokens $tokens, array $assertCall): void
}

$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;
}

$isPositive = 'asserttrue' === $assertCall['loweredName'];

$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])) {
if (false !== self::$fixMap[$content][$isPositive]) {
$tokens[$assertCall['index']] = new Token([T_STRING, self::$fixMap[$content][$isPositive]]);
$this->removeFunctionCall($tokens, $testDefaultNamespaceTokenIndex, $testIndex, $testOpenIndex, $testCloseIndex);
$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;
}

Expand All @@ -274,6 +356,7 @@ private function fixAssertTrueFalse(Tokens $tokens, array $assertCall): void

$tokens->clearTokenAndMergeSurroundingWhitespace($testCloseIndex);
$commaIndex = $tokens->getPrevMeaningfulToken($testCloseIndex);

if ($tokens[$commaIndex]->equals(',')) {
$tokens->removeTrailingWhitespace($commaIndex);
$tokens->clearAt($commaIndex);
Expand All @@ -283,7 +366,7 @@ private function fixAssertTrueFalse(Tokens $tokens, array $assertCall): void
$tokens->insertAt($testOpenIndex + 1, new Token([T_WHITESPACE, ' ']));
}

if (false !== $testDefaultNamespaceTokenIndex) {
if (null !== $testDefaultNamespaceTokenIndex) {
$tokens->clearTokenAndMergeSurroundingWhitespace($testDefaultNamespaceTokenIndex);
}
}
Expand All @@ -303,37 +386,41 @@ private function fixAssertSameEquals(Tokens $tokens, array $assertCall): void

// @ $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 = false;
$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;
}
Expand Down Expand Up @@ -390,23 +477,61 @@ private function getPreviousAssertCall(Tokens $tokens, int $startIndex, int $end
}
}

/**
* @param false|int $callNSIndex
*/
private function removeFunctionCall(Tokens $tokens, $callNSIndex, int $callIndex, int $openIndex, int $closeIndex): void
private function removeFunctionCall(Tokens $tokens, ?int $callNSIndex, int $callIndex, int $openIndex, int $closeIndex): void
{
$tokens->clearTokenAndMergeSurroundingWhitespace($callIndex);
if (false !== $callNSIndex) {

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);
}

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);
}

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;
}
}
1 change: 1 addition & 0 deletions tests/AutoReview/FixerFactoryTest.php
Expand Up @@ -145,6 +145,7 @@ public function provideFixersPriorityCases(): array
[$fixers['modernize_strpos'], $fixers['no_trailing_whitespace']],
[$fixers['modernize_strpos'], $fixers['not_operator_with_space']],
[$fixers['modernize_strpos'], $fixers['not_operator_with_successor_space']],
[$fixers['modernize_strpos'], $fixers['php_unit_dedicate_assert']],
[$fixers['modernize_strpos'], $fixers['single_space_after_construct']],
[$fixers['multiline_whitespace_before_semicolons'], $fixers['space_after_semicolon']],
[$fixers['native_constant_invocation'], $fixers['global_namespace_import']],
Expand Down

0 comments on commit 7c09cca

Please sign in to comment.