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

Mutator/mbstring #664

Merged
merged 23 commits into from Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2bcbf0e
#654 mbstring mutator
majkel89 Mar 10, 2019
66883f7
#654 mbstring mutator configuration
majkel89 Mar 10, 2019
150ee97
#654 fix code analysis issues
majkel89 Mar 10, 2019
97d64bc
#654 update json scheme for MBString mutator
majkel89 Mar 10, 2019
4526990
#654 fix spaceing
majkel89 Mar 10, 2019
733b40e
#654 remove mutants
majkel89 Mar 10, 2019
f6f7ff4
#654 drop ereg functions in favoure of preg functions
majkel89 Mar 12, 2019
6643309
#654 fix styles
majkel89 Mar 13, 2019
8d1b607
Merge remote-tracking branch 'upstream/master' into mutator/mbstring
majkel89 Mar 13, 2019
21f83cb
#654 drop support fro mb_ereg* functions
majkel89 Mar 13, 2019
13af845
#654 mutate mb_convert_case with integer mode
majkel89 Mar 13, 2019
c4fdde9
#654 remove functions that cannot be easily mapped
majkel89 Mar 18, 2019
e635f0c
Merge remote-tracking branch 'upstream/master' into mutator/mbstring
majkel89 Mar 18, 2019
1d6bb5e
#654 rm mb_split from json schema
majkel89 Mar 21, 2019
7420b4c
#654 rm mb_strrichr from json schema
majkel89 Mar 21, 2019
4b513a1
Merge branch 'master' into mutator/mbstring
majkel89 Mar 23, 2019
1789a0b
Merge branch 'master' into mutator/mbstring
majkel89 Mar 26, 2019
c683158
#654 code style fixes
majkel89 Mar 26, 2019
cacb538
#654 simplify MBString mutator
majkel89 Mar 27, 2019
3b486ca
#654 remove getFunctionName fx
majkel89 Mar 28, 2019
a6a4e4b
#654 add tests cases for capitalization
majkel89 Mar 30, 2019
2205316
#654 add test for calling functions via variable
majkel89 Mar 30, 2019
4dadcf2
Merge remote-tracking branch 'upstream/master' into mutator/mbstring
majkel89 Mar 30, 2019
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
75 changes: 75 additions & 0 deletions resources/schema.json
Expand Up @@ -144,6 +144,81 @@
}
]
}
},
"MBString": {
"type": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"settings": {
"type": "object",
"additionalProperties": false,
"properties": {
"mb_chr": {
"type": "boolean"
},
"mb_ord": {
"type": "boolean"
},
"mb_parse_str": {
"type": "boolean"
},
"mb_send_mail": {
"type": "boolean"
},
"mb_strcut": {
"type": "boolean"
},
"mb_stripos": {
"type": "boolean"
},
"mb_stristr": {
"type": "boolean"
},
"mb_strlen": {
"type": "boolean"
},
"mb_strpos": {
"type": "boolean"
},
"mb_strrchr": {
"type": "boolean"
},
"mb_strripos": {
"type": "boolean"
},
"mb_strrpos": {
"type": "boolean"
},
"mb_strstr": {
"type": "boolean"
},
"mb_strtolower": {
"type": "boolean"
},
"mb_strtoupper": {
"type": "boolean"
},
"mb_substr_count": {
"type": "boolean"
},
"mb_substr": {
"type": "boolean"
},
"mb_convert_case": {
"type": "boolean"
}
}
}
}
}
]
}
}
}
},
Expand Down
196 changes: 196 additions & 0 deletions src/Mutator/Extensions/MBString.php
@@ -0,0 +1,196 @@
<?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 MBString extends Mutator
{
private $converters;

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

$settings = $this->getSettings();

$this->setupConverters($settings);
}

/**
* @param 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 = [
'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(),
majkel89 marked this conversation as resolved.
Show resolved Hide resolved
];

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

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

private function mapFunction(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
{
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
{
return function (Node\Expr\FuncCall $node): Generator {
$modeValue = $this->getConvertCaseModeValue($node);

if ($modeValue === null) {
return;
}

$functionName = $this->getConvertCaseFunctionName($modeValue);

if ($functionName === null) {
return;
}

yield $this->mapFunctionCall($node, $functionName, [$node->args[0]]);
};
}

private function getConvertCaseModeValue(Node\Expr\FuncCall $node): ?int
{
if (\count($node->args) < 2) {
return null;
}

$mode = $node->args[1]->value;

if ($mode instanceof Node\Scalar\LNumber) {
return $mode->value;
}

if ($mode instanceof Node\Expr\ConstFetch) {
return \constant($mode->name->toString());
}

return null;
}

private function getConvertCaseFunctionName(int $mode): ?string
{
if ($this->isInMbCaseMode($mode, 'MB_CASE_UPPER', 'MB_CASE_UPPER_SIMPLE')) {
return 'strtoupper';
}

if ($this->isInMbCaseMode($mode, 'MB_CASE_LOWER', 'MB_CASE_LOWER_SIMPLE', 'MB_CASE_FOLD', 'MB_CASE_FOLD_SIMPLE')) {
return 'strtolower';
}

if ($this->isInMbCaseMode($mode, 'MB_CASE_TITLE', 'MB_CASE_TITLE_SIMPLE')) {
return 'ucwords';
}

return null;
}

private function isInMbCaseMode(int $mode, string ...$cases): bool
{
foreach ($cases as $constant) {
if (\defined($constant) && \constant($constant) === $mode) {
return true;
}
}

return false;
}

private function mapFunctionCall(Node\Expr\FuncCall $node, string $newFuncName, array $args): Node\Expr\FuncCall
{
return new Node\Expr\FuncCall(
new Node\Name($newFuncName, $node->name->getAttributes()),
$args,
$node->getAttributes()
);
}
}
9 changes: 9 additions & 0 deletions src/Mutator/Util/MutatorProfile.php
Expand Up @@ -60,6 +60,7 @@ final class MutatorProfile
'@zero_iteration' => self::ZERO_ITERATION,
'@cast' => self::CAST,
'@unwrap' => self::UNWRAP,
'@extensions' => self::EXTENSIONS,

//Special Profiles
'@default' => self::DEFAULT,
Expand Down Expand Up @@ -232,6 +233,10 @@ final class MutatorProfile
Mutator\Unwrap\UnwrapUcWords::class,
];

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

public const DEFAULT = [
'@arithmetic',
'@boolean',
Expand All @@ -246,6 +251,7 @@ final class MutatorProfile
'@return_value',
'@sort',
'@zero_iteration',
'@extensions',
];

public const FULL_MUTATOR_LIST = [
Expand Down Expand Up @@ -395,5 +401,8 @@ final class MutatorProfile
'UnwrapTrim' => Mutator\Unwrap\UnwrapTrim::class,
'UnwrapUcFirst' => Mutator\Unwrap\UnwrapUcFirst::class,
'UnwrapUcWords' => Mutator\Unwrap\UnwrapUcWords::class,

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