Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PhpUnitDedicateAssertFixer - add "assertStringContainsString" and "as… #6029

Merged
merged 1 commit into from
Sep 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Fixer/Alias/ModernizeStrposFixer.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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