Skip to content

Commit

Permalink
New mutator for bcmath (#678)
Browse files Browse the repository at this point in the history
* #658 bcmath mutator

* #658 update dev dependencies

* #658 added required tests, refactoring

* cr fixes (#658)

* properly name mappers generators #678
  • Loading branch information
majkel89 authored and maks-rafalko committed Apr 20, 2019
1 parent 46348d2 commit 91f568d
Show file tree
Hide file tree
Showing 5 changed files with 442 additions and 22 deletions.
48 changes: 48 additions & 0 deletions resources/schema.json
Expand Up @@ -145,6 +145,54 @@
]
}
},
"BCMath": {
"type": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"settings": {
"type": "object",
"additionalProperties": false,
"properties": {
"bcadd": {
"type": "boolean"
},
"bccomp": {
"type": "boolean"
},
"bcdiv": {
"type": "boolean"
},
"bcmod": {
"type": "boolean"
},
"bcmul": {
"type": "boolean"
},
"bcpow": {
"type": "boolean"
},
"bcsub": {
"type": "boolean"
},
"bcsqrt": {
"type": "boolean"
},
"bcpowmod": {
"type": "boolean"
}
}
}
}
}
]
}
},
"MBString": {
"type": {
"anyOf": [
Expand Down
161 changes: 161 additions & 0 deletions src/Mutator/Extensions/BCMath.php
@@ -0,0 +1,161 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017-2019, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Mutator\Extensions;

use Generator;
use Infection\Mutator\Util\Mutator;
use Infection\Mutator\Util\MutatorConfig;
use PhpParser\Node;

/**
* @internal
*/
final class BCMath extends Mutator
{
private $converters;

public function __construct(MutatorConfig $config)
{
parent::__construct($config);

$settings = $this->getSettings();

$this->setupConverters($settings);
}

/**
* @param Node|Node\Expr\FuncCall $node
*
* @return Node|Node[]|Generator
*/
public function mutate(Node $node)
{
yield from $this->converters[$node->name->toLowerString()]($node);
}

protected function mutatesNode(Node $node): bool
{
if (!$node instanceof Node\Expr\FuncCall || !$node->name instanceof Node\Name) {
return false;
}

return isset($this->converters[$node->name->toLowerString()]);
}

private function setupConverters(array $functionsMap): void
{
$converters = [
'bcadd' => $this->makeCheckingMinArgsMapper(2, $this->makeCastToStringMapper(
$this->makeBinaryOperatorMapper(Node\Expr\BinaryOp\Plus::class)
)),
'bcdiv' => $this->makeCheckingMinArgsMapper(2, $this->makeCastToStringMapper(
$this->makeBinaryOperatorMapper(Node\Expr\BinaryOp\Div::class)
)),
'bcmod' => $this->makeCheckingMinArgsMapper(2, $this->makeCastToStringMapper(
$this->makeBinaryOperatorMapper(Node\Expr\BinaryOp\Mod::class)
)),
'bcmul' => $this->makeCheckingMinArgsMapper(2, $this->makeCastToStringMapper(
$this->makeBinaryOperatorMapper(Node\Expr\BinaryOp\Mul::class)
)),
'bcpow' => $this->makeCheckingMinArgsMapper(2, $this->makeCastToStringMapper(
$this->makeBinaryOperatorMapper(Node\Expr\BinaryOp\Pow::class)
)),
'bcsub' => $this->makeCheckingMinArgsMapper(2, $this->makeCastToStringMapper(
$this->makeBinaryOperatorMapper(Node\Expr\BinaryOp\Minus::class)
)),
'bcsqrt' => $this->makeCheckingMinArgsMapper(1, $this->makeCastToStringMapper(
$this->makeSquareRootsMapper()
)),
'bcpowmod' => $this->makeCheckingMinArgsMapper(3, $this->makeCastToStringMapper(
$this->makePowerModuloMapper()
)),
'bccomp' => $this->makeCheckingMinArgsMapper(2,
$this->makeBinaryOperatorMapper(Node\Expr\BinaryOp\Spaceship::class)
),
];

$functionsToRemove = \array_filter($functionsMap, static function (bool $isOn): bool {
return !$isOn;
});

$this->converters = \array_diff_key($converters, $functionsToRemove);
}

private function makeCheckingMinArgsMapper(int $minimumArgsCount, callable $converter)
{
return static function (Node\Expr\FuncCall $node) use ($minimumArgsCount, $converter): Generator {
if (\count($node->args) >= $minimumArgsCount) {
yield from $converter($node);
}
};
}

private function makeCastToStringMapper(callable $converter): callable
{
return static function (Node\Expr\FuncCall $node) use ($converter): Generator {
foreach ($converter($node) as $newNode) {
yield new Node\Expr\Cast\String_($newNode);
}
};
}

private function makeBinaryOperatorMapper(string $operator): callable
{
return static function (Node\Expr\FuncCall $node) use ($operator): Generator {
yield new $operator($node->args[0]->value, $node->args[1]->value);
};
}

private function makeSquareRootsMapper(): callable
{
return static function (Node\Expr\FuncCall $node): Generator {
yield new Node\Expr\FuncCall(new Node\Name('\sqrt'), [$node->args[0]]);
};
}

private function makePowerModuloMapper(): callable
{
return static function (Node\Expr\FuncCall $node): Generator {
yield new Node\Expr\BinaryOp\Mod(
new Node\Expr\FuncCall(
new Node\Name('\pow'),
[$node->args[0], $node->args[1]]
),
$node->args[2]->value
);
};
}
}
44 changes: 22 additions & 22 deletions src/Mutator/Extensions/MBString.php
Expand Up @@ -78,48 +78,48 @@ protected function mutatesNode(Node $node): bool
private function setupConverters(array $functionsMap): void
{
$converters = [
'mb_chr' => $this->mapFunctionAndRemoveExtraArgs('chr', 1),
'mb_ord' => $this->mapFunctionAndRemoveExtraArgs('ord', 1),
'mb_parse_str' => $this->mapFunction('parse_str'),
'mb_send_mail' => $this->mapFunction('mail'),
'mb_strcut' => $this->mapFunctionAndRemoveExtraArgs('substr', 3),
'mb_stripos' => $this->mapFunctionAndRemoveExtraArgs('stripos', 3),
'mb_stristr' => $this->mapFunctionAndRemoveExtraArgs('stristr', 3),
'mb_strlen' => $this->mapFunctionAndRemoveExtraArgs('strlen', 1),
'mb_strpos' => $this->mapFunctionAndRemoveExtraArgs('strpos', 3),
'mb_strrchr' => $this->mapFunctionAndRemoveExtraArgs('strrchr', 2),
'mb_strripos' => $this->mapFunctionAndRemoveExtraArgs('strripos', 3),
'mb_strrpos' => $this->mapFunctionAndRemoveExtraArgs('strrpos', 3),
'mb_strstr' => $this->mapFunctionAndRemoveExtraArgs('strstr', 3),
'mb_strtolower' => $this->mapFunctionAndRemoveExtraArgs('strtolower', 1),
'mb_strtoupper' => $this->mapFunctionAndRemoveExtraArgs('strtoupper', 1),
'mb_substr_count' => $this->mapFunctionAndRemoveExtraArgs('substr_count', 2),
'mb_substr' => $this->mapFunctionAndRemoveExtraArgs('substr', 3),
'mb_convert_case' => $this->mapConvertCase(),
'mb_chr' => $this->makeFunctionAndRemoveExtraArgsMapper('chr', 1),
'mb_ord' => $this->makeFunctionAndRemoveExtraArgsMapper('ord', 1),
'mb_parse_str' => $this->makeFunctionMapper('parse_str'),
'mb_send_mail' => $this->makeFunctionMapper('mail'),
'mb_strcut' => $this->makeFunctionAndRemoveExtraArgsMapper('substr', 3),
'mb_stripos' => $this->makeFunctionAndRemoveExtraArgsMapper('stripos', 3),
'mb_stristr' => $this->makeFunctionAndRemoveExtraArgsMapper('stristr', 3),
'mb_strlen' => $this->makeFunctionAndRemoveExtraArgsMapper('strlen', 1),
'mb_strpos' => $this->makeFunctionAndRemoveExtraArgsMapper('strpos', 3),
'mb_strrchr' => $this->makeFunctionAndRemoveExtraArgsMapper('strrchr', 2),
'mb_strripos' => $this->makeFunctionAndRemoveExtraArgsMapper('strripos', 3),
'mb_strrpos' => $this->makeFunctionAndRemoveExtraArgsMapper('strrpos', 3),
'mb_strstr' => $this->makeFunctionAndRemoveExtraArgsMapper('strstr', 3),
'mb_strtolower' => $this->makeFunctionAndRemoveExtraArgsMapper('strtolower', 1),
'mb_strtoupper' => $this->makeFunctionAndRemoveExtraArgsMapper('strtoupper', 1),
'mb_substr_count' => $this->makeFunctionAndRemoveExtraArgsMapper('substr_count', 2),
'mb_substr' => $this->makeFunctionAndRemoveExtraArgsMapper('substr', 3),
'mb_convert_case' => $this->makeConvertCaseMapper(),
];

$functionsToRemove = \array_filter($functionsMap, static function ($isOn) {
$functionsToRemove = \array_filter($functionsMap, static function (bool $isOn): bool {
return !$isOn;
});

$this->converters = \array_diff_key($converters, $functionsToRemove);
}

private function mapFunction(string $newFunctionName): callable
private function makeFunctionMapper(string $newFunctionName): callable
{
return function (Node\Expr\FuncCall $node) use ($newFunctionName): Generator {
yield $this->mapFunctionCall($node, $newFunctionName, $node->args);
};
}

private function mapFunctionAndRemoveExtraArgs(string $newFunctionName, int $argsAtMost): callable
private function makeFunctionAndRemoveExtraArgsMapper(string $newFunctionName, int $argsAtMost): callable
{
return function (Node\Expr\FuncCall $node) use ($newFunctionName, $argsAtMost): Generator {
yield $this->mapFunctionCall($node, $newFunctionName, \array_slice($node->args, 0, $argsAtMost));
};
}

private function mapConvertCase(): callable
private function makeConvertCaseMapper(): callable
{
return function (Node\Expr\FuncCall $node): Generator {
$modeValue = $this->getConvertCaseModeValue($node);
Expand Down
2 changes: 2 additions & 0 deletions src/Mutator/Util/MutatorProfile.php
Expand Up @@ -236,6 +236,7 @@ final class MutatorProfile
];

public const EXTENSIONS = [
Mutator\Extensions\BCMath::class,
Mutator\Extensions\MBString::class,
];

Expand Down Expand Up @@ -407,6 +408,7 @@ final class MutatorProfile
'UnwrapUcWords' => Mutator\Unwrap\UnwrapUcWords::class,

// Extensions
'BCMath' => Mutator\Extensions\BCMath::class,
'MBString' => Mutator\Extensions\MBString::class,
];
}

0 comments on commit 91f568d

Please sign in to comment.