8000 Custom mutator generator by maks-rafalko · Pull Request #1969 · infection/infection · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Custom mutator generator #1969

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

Merged
merged 25 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7aa03ab
Add custom mutator command
maks-rafalko May 20, 2024
9dbdaf1
Move `BaseMutatorTestCase` to Infection\Testing namespace
maks-rafalko May 20, 2024
c1841e4
Update failure message for BaseMutatorTestCase
maks-rafalko May 20, 2024
e708ea6
Fix autoreview analysis
maks-rafalko May 20, 2024
c3c5ab1
Fix type
maks-rafalko May 20, 2024
0e2d370
Exclude template files from mutating
maks-rafalko May 20, 2024
3191313
Rename `$mutator` to `$mutatorName`
maks-rafalko May 26, 2024
8000 92d5ac6
Fix typo
maks-rafalko May 26, 2024
d23b6c7
Move asking mutator name to separate private function
maks-rafalko May 26, 2024
0c63af7
Use hardcoded paths instead of Finder
maks-rafalko May 26, 2024
7dd7419
Use static methods
maks-rafalko May 26, 2024
969cd70
Create helper method to dump templates to project to improve readability
maks-rafalko May 26, 2024
39c46a2
Fix type
maks-rafalko May 26, 2024
317bd3d
Reword info message
maks-rafalko May 26, 2024
9a97461
Move `getDefinition()` to the top of the mutator class
maks-rafalko May 26, 2024
f5d986d
Inline `Mutator` property type
maks-rafalko May 26, 2024
30f6764
Rename base test method
maks-rafalko May 26, 2024
2e9faf5
Remove outdated TODOs
maks-rafalko May 26, 2024
fa8b489
Inline mutator node type
maks-rafalko May 26, 2024
46777a0
Fix phpstan issues
maks-rafalko May 26, 2024
a61768b
Rename command to `make:custom-mutator`
maks-rafalko May 26, 2024
7c9afb1
Create interact() method for new command, add tests
maks-rafalko May 26, 2024
ebb0ca8
Rename command to `make:mutator`
maks-rafalko May 27, 2024
972ec62
Fix typo
maks-rafalko May 27, 2024
7d3fdac
Allow custom mutator to be used in `--mutators=X,Y,Z` option
maks-rafalko May 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions devTools/phpstan-src-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
parameters:
ignoreErrors:
-
message: "#^Anonymous function should return string but returns string\\|null\\.$#"
count: 1
path: ../src/Command/MakeCustomMutatorCommand.php

-
message: "#^Cannot call method askQuestion\\(\\) on Infection\\\\Console\\\\IO\\|null\\.$#"
count: 1
path: ../src/Command/MakeCustomMutatorCommand.php

-
message: "#^Parameter \\#1 \\$array of static method Webmozart\\\\Assert\\\\Assert\\:\\:keyExists\\(\\) expects array, iterable\\<\\(int\\|string\\), string\\>\\|null given\\.$#"
count: 2
Expand Down Expand Up @@ -79,3 +89,83 @@ parameters:
message: "#^Cannot access offset 0 on iterable\\<\\(int\\|string\\), string\\>\\|null\\.$#"
count: 1
path: ../src/TestFramework/VersionParser.php

-
message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertCount\\(\\)\\.$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertSame\\(\\)\\.$#"
count: 2
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Dynamic call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:fail\\(\\)\\.$#"
count: 2
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Method Infection\\\\Testing\\\\BaseMutatorTestCase\\:\\:createMutator\\(\\) has parameter \\$settings with no value type specified in iterable type array\\.$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Method Infection\\\\Testing\\\\BaseMutatorTestCase\\:\\:createMutator\\(\\) return type with generic interface Infection\\\\Mutator\\\\Mutator does not specify its types\\: TNode$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Method Infection\\\\Testing\\\\BaseMutatorTestCase\\:\\:getMutationsFromCode\\(\\) has parameter \\$settings with no value type specified in iterable type array\\.$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Method Infection\\\\Testing\\\\BaseMutatorTestCase\\:\\:mutate\\(\\) has parameter \\$settings with no value type specified in iterable type array\\.$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Parameter \\#1 \\$nodes of method PhpParser\\\\NodeTraverserInterface\\:\\:traverse\\(\\) expects array\\<PhpParser\\\\Node\\>, array\\<PhpParser\\\\Node\\\\Stmt\\>\\|null given\\.$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Parameter \\#1 \\$resolvedMutators of method Infection\\\\Mutator\\\\MutatorFactory\\:\\:create\\(\\) expects array\\<class\\-string\\<Infection\\\\Mutator\\\\ConfigurableMutator\\<PhpParser\\\\Node\\>\\>, array\\>, non\\-empty\\-array\\<string, array\\{settings\\: array\\}\\> given\\.$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Parameter \\#2 \\$fileAst of class Infection\\\\Testing\\\\SimpleMutationsCollectorVisitor constructor expects array\\<PhpParser\\\\Node\\>, array\\<PhpParser\\\\Node\\\\Stmt\\>\\|null given\\.$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Property Infection\\\\Testing\\\\BaseMutatorTestCase\\:\\:\\$mutator with generic interface Infection\\\\Mutator\\\\Mutator does not specify its types\\: TNode$#"
count: 1
path: ../src/Testing/BaseMutatorTestCase.php

-
message: "#^Class Infection\\\\Testing\\\\SimpleMutation extends @final class Infection\\\\Mutation\\\\Mutation\\.$#"
count: 1
path: ../src/Testing/SimpleMutation.php

-
message: "#^Infection\\\\Testing\\\\SimpleMutation\\:\\:__construct\\(\\) does not call parent constructor from Infection\\\\Mutation\\\\Mutation\\.$#"
count: 1
path: ../src/Testing/SimpleMutation.php

-
message: "#^Method Infection\\\\Testing\\\\SimpleMutation\\:\\:__construct\\(\\) has parameter \\$mutator with generic interface Infection\\\\Mutator\\\\Mutator but does not specify its types\\: TNode$#"
count: 1
path: ../src/Testing/SimpleMutation.php

-
message: "#^Method Infection\\\\Testing\\\\SimpleMutation\\:\\:getMutator\\(\\) return type with generic interface Infection\\\\Mutator\\\\Mutator does not specify its types\\: TNode$#"
count: 1
path: ../src/Testing/SimpleMutation.php

-
message: "#^Cannot access offset 1 on iterable\\<\\(int\\|string\\), string\\>\\|null\\.$#"
count: 1
path: ../src/Testing/SourceTestClassNameScheme.php
2 changes: 2 additions & 0 deletions devTools/phpstan-src.neon
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ parameters:
- %currentWorkingDirectory%/src/FileSystem/DummyFileSystem.php
- %currentWorkingDirectory%/src/FileSystem/DummySymfony5FileSystem.php
- %currentWorkingDirectory%/src/FileSystem/DummySymfony6FileSystem.php
- %currentWorkingDirectory%/src/CustomMutator/templates/__Name__.php
- %currentWorkingDirectory%/src/CustomMutator/templates/__Name__Test.php
stubFiles:
- phpstan.stub
treatPhpDocTypesAsCertain: false
4 changes: 4 additions & 0 deletions devTools/phpstan-tests.neon
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ parameters:
message: "#^Instantiated class Infection\\\\Tests\\\\Fixtures\\\\Console\\\\FakeOutput not found\\.$#"
count: 11
path: ../tests/*
-
message: "#^PHPDoc tag @param for parameter \\$values contains unresolvable type\\.$#"
count: 1
path: ../tests/phpunit/MockedContainer.php
level: 4
paths:
- ../tests/phpunit
Expand Down
4 changes: 3 additions & 1 deletion infection.json5
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
excludes: [
"FileSystem/DummyFileSystem.php",
"FileSystem/DummySymfony5FileSystem.php",
"FileSystem/DummySymfony6FileSystem.php"
"FileSystem/DummySymfony6FileSystem.php",
"CustomMutator/templates/__Name__.php",
"CustomMutator/templates/__Name__Test.php",
]
},
"logs": {
Expand Down
2 changes: 2 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
<directory name="src" />
<ignoreFiles>
<directory name="src/PhpParser" />
<directory name="src/Testing" />
<directory name="src/CustomMutator/templates" />
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
Expand Down
2 changes: 1 addition & 1 deletion src/Command/BaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
*/
abstract class BaseCommand extends Command
{
private ?IO $io = null;
protected ?IO $io = null;

final public function getApplication(): Application
{
Expand Down
150 changes: 150 additions & 0 deletions src/Command/MakeCustomMutatorCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?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\Command;

use function basename;
use Infection\Console\IO;
use RuntimeException;
use function Safe\file_get_contents;
use function Safe\getcwd;
use function sprintf;
use function str_replace;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use function trim;
use function ucfirst;

/**
* @internal
*/
final class MakeCustomMutatorCommand extends BaseCommand
{
private const MUTATOR_NAME_ARGUMENT = 'Mutator name';

protected function configure(): void
{
$this
->setName('make:mutator')
->setDescription('Creates a custom mutator')
->addArgument(self::MUTATOR_NAME_ARGUMENT, InputArgument::REQUIRED);
}

protected function interact(InputInterface $input, OutputInterface $output): void
{
$mutatorName = $input->getArgument(self::MUTATOR_NAME_ARGUMENT);

if ($this->mutatorNameIsEmpty($mutatorName)) {
$mutatorName = $this->askMutatorName();

$input->setArgument(self::MUTATOR_NAME_ARGUMENT, $mutatorName);
}
}

protected function executeCommand(IO $io): bool
{
$mutatorName = ucfirst(trim((string) $io->getInput()->getArgument(self::MUTATOR_NAME_ARGUMENT)));

Check warning on line 80 in src/Command/MakeCustomMutatorCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "CastString": --- Original +++ New @@ @@ } protected function executeCommand(IO $io): bool { - $mutatorName = ucfirst(trim((string) $io->getInput()->getArgument(self::MUTATOR_NAME_ARGUMENT))); + $mutatorName = ucfirst(trim($io->getInput()->getArgument(self::MUTATOR_NAME_ARGUMENT))); $templateFilePaths = [__DIR__ . '/../CustomMutator/templates/__Name__.php', __DIR__ . '/../CustomMutator/templates/__Name__Test.php']; $generatedFilePaths = $this->createProjectFilesFromTemplates($templateFilePaths, $mutatorName); $io->title('Generated files');

$templateFilePaths = [
__DIR__ . '/../CustomMutator/templates/__Name__.php',
__DIR__ . '/../CustomMutator/templates/__Name__Test.php',
];

$generatedFilePaths = $this->createProjectFilesFromTemplates($templateFilePaths, $mutatorName);

$io->title('Generated files');
$io->listing($generatedFilePaths);
$io->success(

Check warning on line 91 in src/Command/MakeCustomMutatorCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ $generatedFilePaths = $this->createProjectFilesFromTemplates($templateFilePaths, $mutatorName); $io->title('Generated files'); $io->listing($generatedFilePaths); - $io->success(sprintf('Base classes for the mutator "%s" were created. Complete the missing parts inside them.', $mutatorName)); + return true; } private static function replaceNameVariable(string $rectorName, string $contents): string
sprintf('Base classes for the mutator "%s" were created. Complete the missing parts inside them.', $mutatorName),
);

return true;
}

private static function replaceNameVariable(string $rectorName, string $contents): string
{
return str_replace('__Name__', $rectorName, $contents);
}

private function askMutatorName(): mixed
{
$question = new Question('What mutator do you wish to create (e.g. `AnyStringToInfectedMutator`)?');

$question->setValidator(function (?string $answer): string {

Check warning on line 107 in src/Command/MakeCustomMutatorCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ private function askMutatorName(): mixed { $question = new Question('What mutator do you wish to create (e.g. `AnyStringToInfectedMutator`)?'); - $question->setValidator(function (?string $answer): string { - if ($this->mutatorNameIsEmpty($answer)) { - throw new RuntimeException('Mutator name is mandatory.'); - } - return $answer; - }); + return $this->io->askQuestion($question); } private function mutatorNameIsEmpty(?string $mutatorName): bool
if ($this->mutatorNameIsEmpty($answer)) {
throw new RuntimeException('Mutator name is mandatory.');
}

return $answer;
});

return $this->io->askQuestion(
$question,
);
}

private function mutatorNameIsEmpty(?string $mutatorName): bool
{
return $mutatorName === null || trim($mutatorName) === '';

Check warning on line 122 in src/Command/MakeCustomMutatorCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "UnwrapTrim": --- Original +++ New @@ @@ } private function mutatorNameIsEmpty(?string $mutatorName): bool { - return $mutatorName === null || trim($mutatorName) === ''; + return $mutatorName === null || $mutatorName === ''; } /** * @param list<string> $filePaths
}

/**
* @param list<string> $filePaths
* @return list<string>
*/
private function createProjectFilesFromTemplates(array $filePaths, string $mutatorName): array
{
$currentDirectory = getcwd();
$generatedFilePaths = [];

$fileSystem = $this->getApplication()->getContainer()->getFileSystem();

foreach ($filePaths as $filePath) {
// replace __Name__ with $mutatorName
$newContent = self::replaceNameVariable($mutatorName, file_get_contents($filePath));
$replacedNamePath = self::replaceNameVariable($mutatorName, basename($filePath));

$newFilePath = $currentDirectory . '/src/Mutator/' . $replacedNamePath;

Check warning on line 141 in src/Command/MakeCustomMutatorCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ // replace __Name__ with $mutatorName $newContent = self::replaceNameVariable($mutatorName, file_get_contents($filePath)); $replacedNamePath = self::replaceNameVariable($mutatorName, basename($filePath)); - $newFilePath = $currentDirectory . '/src/Mutator/' . $replacedNamePath; + $newFilePath = '/src/Mutator/' . $currentDirectory . $replacedNamePath; $fileSystem->dumpFile($newFilePath, $newContent); $generatedFilePaths[] = $newFilePath; }

Check warning on line 141 in src/Command/MakeCustomMutatorCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ // replace __Name__ with $mutatorName $newContent = self::replaceNameVariable($mutatorName, file_get_contents($filePath)); $replacedNamePath = self::replaceNameVariable($mutatorName, basename($filePath)); - $newFilePath = $currentDirectory . '/src/Mutator/' . $replacedNamePath; + $newFilePath = $currentDirectory . $replacedNamePath; $fileSystem->dumpFile($newFilePath, $newContent); $generatedFilePaths[] = $newFilePath; }

Check warning on line 141 in src/Command/MakeCustomMutatorCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ // replace __Name__ with $mutatorName $newContent = self::replaceNameVariable($mutatorName, file_get_contents($filePath)); $replacedNamePath = self::replaceNameVariable($mutatorName, basename($filePath)); - $newFilePath = $currentDirectory . '/src/Mutator/' . $replacedNamePath; + $newFilePath = '/src/Mutator/' . $replacedNamePath; $fileSystem->dumpFile($newFilePath, $newContent); $generatedFilePaths[] = $newFilePath; }

Check warning on line 141 in src/Command/MakeCustomMutatorCommand.php DDEA

View workflow job for this annotation

GitHub Actions / Mutation Testing Code Review Annotations 8.1

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ // replace __Name__ with $mutatorName $newContent = self::replaceNameVariable($mutatorName, file_get_contents($filePath)); $replacedNamePath = self::replaceNameVariable($mutatorName, basename($filePath)); - $newFilePath = $currentDirectory . '/src/Mutator/' . $replacedNamePath; + $newFilePath = $currentDirectory . $replacedNamePath . '/src/Mutator/'; $fileSystem->dumpFile($newFilePath, $newContent); $generatedFilePaths[] = $newFilePath; }

$fileSystem->dumpFile($newFilePath, $newContent);

$generatedFilePaths[] = $newFilePath;
}

return $generatedFilePaths;
}
}
2 changes: 2 additions & 0 deletions src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use Composer\InstalledVersions;
use Infection\Command\ConfigureCommand;
use Infection\Command\DescribeCommand;
use Infection\Command\MakeCustomMutatorCommand;
use Infection\Command\RunCommand;
use Infection\Container;
use OutOfBoundsException;
Expand Down Expand Up @@ -104,6 +105,7 @@ protected function getDefaultCommands(): array
new ConfigureCommand(),
new RunCommand(),
new DescribeCommand(),
new MakeCustomMutatorCommand(),
],
);

Expand Down
Loading
Loading
0