Commit
…expression (#1326) * Add an ability to ignore Mutators for source code line(s) by regular expression. Fixes #697 Allows to avoid mutating such lines of code as: ```diff - Assert::notNull($variable); ``` or ```diff - $this->logger->error($message, ['user' => $user]); + $this->logger->error($message, []); ``` and so on. Example of new config setting usage: ```json { "mutators": { "MethodCallRemoval": { "ignoreSourceCodeByRegex": [ "Assert::", "\\$this->logger" ] } } } ``` * Escape delimiters before building regular expression with user's input * Rebase on master, use `MutatorFactory::getMutatorNameForClassName()` * Make public method private, compare with empty array
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,12 +36,13 @@ | |
namespace Infection\Configuration; | ||
|
||
use function array_fill_keys; | ||
use function count; | ||
use function array_key_exists; | ||
use function dirname; | ||
use Infection\Configuration\Entry\PhpUnit; | ||
use Infection\Configuration\Schema\SchemaConfiguration; | ||
use Infection\FileSystem\SourceFileCollector; | ||
use Infection\FileSystem\TmpDirProvider; | ||
use Infection\Mutator\ConfigurableMutator; | ||
use Infection\Mutator\Mutator; | ||
use Infection\Mutator\MutatorFactory; | ||
use Infection\Mutator\MutatorParser; | ||
|
@@ -50,6 +51,7 @@ | |
use OndraM\CiDetector\CiDetector; | ||
use function Safe\sprintf; | ||
use function sys_get_temp_dir; | ||
use Webmozart\Assert\Assert; | ||
use Webmozart\PathUtil\Path; | ||
|
||
/** | ||
|
@@ -121,6 +123,11 @@ public function create( | |
$namespacedTmpDir | ||
); | ||
|
||
$resolvedMutatorsArray = $this->resolveMutators($schema->getMutators(), $mutatorsInput); | ||
This comment has been minimized.
Sorry, something went wrong. |
||
|
||
$mutators = $this->mutatorFactory->create($resolvedMutatorsArray); | ||
$ignoreSourceCodeMutatorsMap = $this->retrieveIgnoreSourceCodeMutatorsMap($resolvedMutatorsArray); | ||
|
||
return new Configuration( | ||
$schema->getTimeout() ?? self::DEFAULT_TIMEOUT, | ||
$schema->getSource()->getDirectories(), | ||
|
@@ -134,7 +141,7 @@ public function create( | |
$logVerbosity, | ||
$namespacedTmpDir, | ||
$this->retrievePhpUnit($schema, $configDir), | ||
$this->retrieveMutators($schema->getMutators(), $mutatorsInput), | ||
$mutators, | ||
$testFramework, | ||
$schema->getBootstrap(), | ||
$initialTestsPhpOptions ?? $schema->getInitialTestsPhpOptions(), | ||
|
@@ -151,10 +158,33 @@ public function create( | |
self::retrieveMinCoveredMsi($minCoveredMsi, $schema), | ||
$msiPrecision, | ||
$threadCount, | ||
$dryRun | ||
$dryRun, | ||
$ignoreSourceCodeMutatorsMap | ||
); | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $schemaMutators | ||
* | ||
* @return array<class-string<Mutator&ConfigurableMutator>, mixed[]> | ||
*/ | ||
private function resolveMutators(array $schemaMutators, string $mutatorsInput): array | ||
{ | ||
if ($schemaMutators === []) { | ||
$schemaMutators = ['@default' => true]; | ||
} | ||
|
||
$parsedMutatorsInput = $this->mutatorParser->parse($mutatorsInput); | ||
|
||
if ($parsedMutatorsInput === []) { | ||
$mutatorsList = $schemaMutators; | ||
} else { | ||
$mutatorsList = array_fill_keys($parsedMutatorsInput, true); | ||
} | ||
|
||
return $this->mutatorResolver->resolve($mutatorsList); | ||
} | ||
|
||
private function retrieveTmpDir( | ||
SchemaConfiguration $schema, | ||
string $configDir | ||
|
@@ -187,30 +217,6 @@ private function retrievePhpUnit(SchemaConfiguration $schema, string $configDir) | |
return $phpUnit; | ||
} | ||
|
||
/** | ||
* @param array<string, mixed> $schemaMutators | ||
* | ||
* @return array<string, Mutator> | ||
*/ | ||
private function retrieveMutators(array $schemaMutators, string $mutatorsInput): array | ||
{ | ||
if (count($schemaMutators) === 0) { | ||
$schemaMutators = ['@default' => true]; | ||
} | ||
|
||
$parsedMutatorsInput = $this->mutatorParser->parse($mutatorsInput); | ||
|
||
if ($parsedMutatorsInput === []) { | ||
$mutatorsList = $schemaMutators; | ||
} else { | ||
$mutatorsList = array_fill_keys($parsedMutatorsInput, true); | ||
} | ||
|
||
return $this->mutatorFactory->create( | ||
$this->mutatorResolver->resolve($mutatorsList) | ||
); | ||
} | ||
|
||
private static function retrieveCoverageBasePath( | ||
?string $existingCoveragePath, | ||
string $configDir, | ||
|
@@ -255,4 +261,26 @@ private static function retrieveMinCoveredMsi(?float $minCoveredMsi, SchemaConfi | |
{ | ||
return $minCoveredMsi ?? $schema->getMinCoveredMsi(); | ||
} | ||
|
||
/** | ||
* @param array<string, mixed[]> $resolvedMutatorsMap | ||
* | ||
* @return array<string, array<int, string>> | ||
*/ | ||
private function retrieveIgnoreSourceCodeMutatorsMap(array $resolvedMutatorsMap): array | ||
This comment has been minimized.
Sorry, something went wrong.
theofidry
Member
|
||
{ | ||
$map = []; | ||
|
||
foreach ($resolvedMutatorsMap as $mutatorClassName => $config) { | ||
if (array_key_exists('ignoreSourceCodeByRegex', $config)) { | ||
This comment has been minimized.
Sorry, something went wrong.
theofidry
Member
|
||
$mutatorName = MutatorFactory::getMutatorNameForClassName($mutatorClassName); | ||
This comment has been minimized.
Sorry, something went wrong.
theofidry
Member
|
||
|
||
Assert::isArray($config['ignoreSourceCodeByRegex']); | ||
|
||
$map[$mutatorName] = $config['ignoreSourceCodeByRegex']; | ||
} | ||
} | ||
|
||
return $map; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
/** | ||
* This code is licensed under the BSD 3-Clause License. | ||
* | ||
* Copyright (c) 2017, 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\Differ; | ||
|
||
use function Safe\preg_match; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
final class DiffSourceCodeMatcher | ||
{ | ||
public function matches(string $diff, string $sourceCodeRegex): bool | ||
{ | ||
$regexWithEscapedDelimiters = str_replace('/', '\/', $sourceCodeRegex); | ||
|
||
return preg_match("/^-\s*{$regexWithEscapedDelimiters}$/mu", $diff) === 1; | ||
} | ||
} |
$resolvedMutators
? It's a bit weird to specify the type in the name