mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-26 12:52:36 +00:00
Add ScanFatalErrors command
This commit is contained in:
parent
03fbc5b0f7
commit
3742dc4b44
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -21,4 +21,7 @@ uuid-migration.json
|
|||
# compiler
|
||||
/compiler/composer.lock
|
||||
/compiler/vendor
|
||||
/tmp
|
||||
/tmp
|
||||
|
||||
# from "scan-fatal-errors" command
|
||||
rector-types.yaml
|
5
abz/First.php
Normal file
5
abz/First.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
class First extends Test {
|
||||
public function remove() {}
|
||||
}
|
5
abz/Second.php
Normal file
5
abz/Second.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
class Second extends Test {
|
||||
public function remove() {}
|
||||
}
|
5
abz/Test.php
Normal file
5
abz/Test.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
class Test {
|
||||
public function remove(): void {}
|
||||
}
|
|
@ -174,7 +174,8 @@
|
|||
"tests/Rector/Namespace_/PseudoNamespaceToNamespaceRector/Source",
|
||||
"tests/Issues/Issue1243/Source",
|
||||
"packages/Autodiscovery/tests/Rector/FileSystem/MoveInterfacesToContractNamespaceDirectoryRector/Expected",
|
||||
"packages/Autodiscovery/tests/Rector/FileSystem/MoveServicesBySuffixToDirectoryRector/Expected"
|
||||
"packages/Autodiscovery/tests/Rector/FileSystem/MoveServicesBySuffixToDirectoryRector/Expected",
|
||||
"abz"
|
||||
],
|
||||
"files": [
|
||||
"packages/DeadCode/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/Source/UserDefined.php",
|
||||
|
|
|
@ -237,3 +237,6 @@ parameters:
|
|||
-
|
||||
message: '#Method Rector\\Symfony\\Rector\\FrameworkBundle\\AbstractToConstructorInjectionRector\:\:getServiceTypeFromMethodCallArgument\(\) should return PHPStan\\Type\\Type but returns PHPStan\\Type\\Type\|null#'
|
||||
path: packages/Symfony/src/Rector/FrameworkBundle/AbstractToConstructorInjectionRector.php
|
||||
|
||||
- '#Parameter \#1 \$error_handler of function set_error_handler expects \(callable\(int, string, string, int, array\)\: bool\)\|null, Closure\(int, string\)\: void given#'
|
||||
- '#Parameter \#1 \$source of method Rector\\Scan\\ErrorScanner\:\:scanSource\(\) expects array<string\>, array<string\>\|string\|null given#'
|
||||
|
|
63
rector.yaml
63
rector.yaml
|
@ -15,66 +15,3 @@ parameters:
|
|||
|
||||
# so Rector code is still PHP 7.2 compatible
|
||||
php_version_features: '7.2'
|
||||
|
||||
rector_recipe:
|
||||
# run "bin/rector create" to create a new Rector + tests from this config
|
||||
package: "NetteToSymfony"
|
||||
name: "FormControlToControllerAndFormTypeRector"
|
||||
node_types:
|
||||
# put main node first, it is used to create namespace
|
||||
- "Class_"
|
||||
description: "Change Form that extends Control to Controller and decoupled FormType"
|
||||
code_before: |
|
||||
<?php
|
||||
|
||||
use Nette\Application\UI\Form;
|
||||
use Nette\Application\UI\Control;
|
||||
|
||||
class SomeForm extends Control
|
||||
{
|
||||
public function createComponentForm()
|
||||
{
|
||||
$form = new Form();
|
||||
$form->addText('name', 'Your name');
|
||||
|
||||
$form->onSuccess[] = [$this, 'processForm'];
|
||||
}
|
||||
|
||||
public function processForm(Form $form)
|
||||
{
|
||||
// process me
|
||||
}
|
||||
}
|
||||
|
||||
code_after: |
|
||||
<?php
|
||||
|
||||
class SomeFormController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route(...)
|
||||
*/
|
||||
public function actionSomeForm(Request $request): Response
|
||||
{
|
||||
$form = $this->createForm(SomeFormType::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSuccess() && $form->isValid()) {
|
||||
// process me
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SomeFormType extends \Symfony\Component\Form\AbstractType
|
||||
{
|
||||
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('name', \Symfony\Component\Form\Extension\Core\Type\TextType::class, [
|
||||
'label' => 'Your name'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
source: # e.g. link to RFC or headline in upgrade guide, 1 or more in the list
|
||||
- "https://symfony.com/doc/current/forms.html#creating-form-classes"
|
||||
set: "nette-to-symfony" # e.g. symfony30, target config to append this rector to
|
||||
|
|
|
@ -65,4 +65,9 @@ final class Option
|
|||
* @var string
|
||||
*/
|
||||
public const SYMFONY_CONTAINER_XML_PATH_PARAMETER = 'symfony_container_xml_path';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const DUMP = 'dump';
|
||||
}
|
||||
|
|
108
src/Console/Command/ScanFatalErrorsCommand.php
Normal file
108
src/Console/Command/ScanFatalErrorsCommand.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Console\Command;
|
||||
|
||||
use Rector\Configuration\Option;
|
||||
use Rector\Console\Shell;
|
||||
use Rector\Scan\ErrorScanner;
|
||||
use Rector\Scan\ScannedErrorToRectorResolver;
|
||||
use Rector\Yaml\YamlPrinter;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symplify\PackageBuilder\Console\Command\CommandNaming;
|
||||
use Symplify\PackageBuilder\Console\ShellCode;
|
||||
|
||||
final class ScanFatalErrorsCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const RECTOR_TYPES_YAML = 'rector-types.yaml';
|
||||
|
||||
/**
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $symfonyStyle;
|
||||
|
||||
/**
|
||||
* @var ScannedErrorToRectorResolver
|
||||
*/
|
||||
private $scannedErrorToRectorResolver;
|
||||
|
||||
/**
|
||||
* @var ErrorScanner
|
||||
*/
|
||||
private $errorScanner;
|
||||
|
||||
/**
|
||||
* @var YamlPrinter
|
||||
*/
|
||||
private $yamlPrinter;
|
||||
|
||||
public function __construct(
|
||||
SymfonyStyle $symfonyStyle,
|
||||
ScannedErrorToRectorResolver $scannedErrorToRectorResolver,
|
||||
ErrorScanner $errorScanner,
|
||||
YamlPrinter $yamlPrinter
|
||||
) {
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
$this->scannedErrorToRectorResolver = $scannedErrorToRectorResolver;
|
||||
$this->errorScanner = $errorScanner;
|
||||
$this->yamlPrinter = $yamlPrinter;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName(CommandNaming::classToName(self::class));
|
||||
|
||||
$this->setDescription('Scan for fatal type errors and dumps config that fixes it');
|
||||
|
||||
$this->addArgument(
|
||||
Option::SOURCE,
|
||||
InputArgument::REQUIRED | InputArgument::IS_ARRAY,
|
||||
'Path to file/directory to process'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
/** @var string[] $source */
|
||||
$source = $input->getArgument(Option::SOURCE);
|
||||
$errors = $this->errorScanner->scanSource($source);
|
||||
|
||||
if ($errors === []) {
|
||||
$this->symfonyStyle->success('No fatal errors found');
|
||||
return ShellCode::SUCCESS;
|
||||
}
|
||||
|
||||
$this->symfonyStyle->section(sprintf('Found %d Errors', count($errors)));
|
||||
foreach ($errors as $error) {
|
||||
$this->symfonyStyle->note($error);
|
||||
}
|
||||
|
||||
$rectorConfiguration = $this->scannedErrorToRectorResolver->processErrors($errors);
|
||||
if ($rectorConfiguration === []) {
|
||||
$this->symfonyStyle->success('No fatal errors found');
|
||||
return ShellCode::SUCCESS;
|
||||
}
|
||||
|
||||
$this->yamlPrinter->printYamlToFile($rectorConfiguration, self::RECTOR_TYPES_YAML);
|
||||
|
||||
$this->symfonyStyle->note(sprintf('New config with types was created in "%s"', self::RECTOR_TYPES_YAML));
|
||||
|
||||
$this->symfonyStyle->success(sprintf(
|
||||
'Now run Rector to refactor your code:%svendor/bin/rector p %s --config %s',
|
||||
PHP_EOL . PHP_EOL,
|
||||
implode(' ', $source),
|
||||
self::RECTOR_TYPES_YAML
|
||||
));
|
||||
|
||||
return Shell::CODE_SUCCESS;
|
||||
}
|
||||
}
|
|
@ -7,11 +7,11 @@ namespace Rector\Console\Command;
|
|||
use Rector\Console\Shell;
|
||||
use Rector\Contract\Rector\RectorInterface;
|
||||
use Rector\Php\TypeAnalyzer;
|
||||
use Rector\Yaml\YamlPrinter;
|
||||
use ReflectionClass;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symplify\PackageBuilder\Console\Command\CommandNaming;
|
||||
|
||||
final class ShowCommand extends AbstractCommand
|
||||
|
@ -31,16 +31,26 @@ final class ShowCommand extends AbstractCommand
|
|||
*/
|
||||
private $typeAnalyzer;
|
||||
|
||||
/**
|
||||
* @var YamlPrinter
|
||||
*/
|
||||
private $yamlPrinter;
|
||||
|
||||
/**
|
||||
* @param RectorInterface[] $rectors
|
||||
*/
|
||||
public function __construct(SymfonyStyle $symfonyStyle, array $rectors, TypeAnalyzer $typeAnalyzer)
|
||||
{
|
||||
public function __construct(
|
||||
SymfonyStyle $symfonyStyle,
|
||||
array $rectors,
|
||||
TypeAnalyzer $typeAnalyzer,
|
||||
YamlPrinter $yamlPrinter
|
||||
) {
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
$this->rectors = $rectors;
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
$this->yamlPrinter = $yamlPrinter;
|
||||
|
||||
parent::__construct();
|
||||
$this->typeAnalyzer = $typeAnalyzer;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
|
@ -60,7 +70,7 @@ final class ShowCommand extends AbstractCommand
|
|||
continue;
|
||||
}
|
||||
|
||||
$configurationYamlContent = Yaml::dump($configuration, 10, 4, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||
$configurationYamlContent = $this->yamlPrinter->printYamlToString($configuration);
|
||||
|
||||
$lines = explode(PHP_EOL, $configurationYamlContent);
|
||||
$indentedContent = ' ' . implode(PHP_EOL . ' ', $lines);
|
||||
|
|
|
@ -80,8 +80,10 @@ final class FilesFinder
|
|||
if ($matchDiff) {
|
||||
$gitDiffFiles = $this->getGitDiff();
|
||||
|
||||
$smartFileInfos = array_filter($smartFileInfos, function ($splFile) use ($gitDiffFiles): bool {
|
||||
return in_array($splFile->getRealPath(), $gitDiffFiles, true);
|
||||
$smartFileInfos = array_filter($smartFileInfos, function (SmartFileInfo $fileInfo) use (
|
||||
$gitDiffFiles
|
||||
): bool {
|
||||
return in_array($fileInfo->getRealPath(), $gitDiffFiles, true);
|
||||
});
|
||||
|
||||
$smartFileInfos = array_values($smartFileInfos);
|
||||
|
|
102
src/Scan/ErrorScanner.php
Normal file
102
src/Scan/ErrorScanner.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Scan;
|
||||
|
||||
use Rector\FileSystem\FilesFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
final class ErrorScanner
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $errors = [];
|
||||
|
||||
/**
|
||||
* @var FilesFinder
|
||||
*/
|
||||
private $filesFinder;
|
||||
|
||||
public function __construct(FilesFinder $filesFinder)
|
||||
{
|
||||
$this->filesFinder = $filesFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $source
|
||||
* @return string[]
|
||||
*/
|
||||
public function scanSource(array $source): array
|
||||
{
|
||||
$this->setErrorHandler();
|
||||
|
||||
$fileInfos = $this->filesFinder->findInDirectoriesAndFiles($source, ['php']);
|
||||
|
||||
$commandLine = 'include "vendor/autoload.php";';
|
||||
|
||||
foreach ($fileInfos as $fileInfo) {
|
||||
$currentCommandLine = $commandLine . PHP_EOL;
|
||||
$currentCommandLine .= sprintf('include "%s";', $fileInfo->getRelativeFilePathFromCwd());
|
||||
|
||||
$currentCommandLine = sprintf("php -r '%s'", $currentCommandLine);
|
||||
$process = Process::fromShellCommandline($currentCommandLine);
|
||||
$process->run();
|
||||
|
||||
if ($process->isSuccessful()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->errors[] = trim($process->getErrorOutput());
|
||||
}
|
||||
|
||||
$this->restoreErrorHandler();
|
||||
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
public function shutdown_function(): void
|
||||
{
|
||||
$error = error_get_last();
|
||||
//check if it's a core/fatal error, otherwise it's a normal shutdown
|
||||
if ($error === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! in_array(
|
||||
$error['type'],
|
||||
[
|
||||
E_ERROR,
|
||||
E_PARSE,
|
||||
E_CORE_ERROR,
|
||||
E_CORE_WARNING,
|
||||
E_COMPILE_ERROR,
|
||||
E_COMPILE_WARNING,
|
||||
E_RECOVERABLE_ERROR,
|
||||
], true
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
print_r($error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/manual/en/function.set-error-handler.php
|
||||
* @see https://stackoverflow.com/a/36638910/1348344
|
||||
*/
|
||||
private function setErrorHandler(): void
|
||||
{
|
||||
register_shutdown_function([$this, 'shutdown_function']);
|
||||
|
||||
set_error_handler(function (int $num, string $error): void {
|
||||
$this->errors[] = $error;
|
||||
});
|
||||
}
|
||||
|
||||
private function restoreErrorHandler(): void
|
||||
{
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
175
src/Scan/ScannedErrorToRectorResolver.php
Normal file
175
src/Scan/ScannedErrorToRectorResolver.php
Normal file
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Scan;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use Rector\Exception\NotImplementedException;
|
||||
use Rector\Rector\ClassMethod\AddReturnTypeDeclarationRector;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeDeclarationRector;
|
||||
use Rector\ValueObject\Scan\Argument;
|
||||
use Rector\ValueObject\Scan\ClassMethodWithArguments;
|
||||
|
||||
final class ScannedErrorToRectorResolver
|
||||
{
|
||||
/**
|
||||
* @see https://regex101.com/r/RbJy9h/1/
|
||||
* @var string
|
||||
*/
|
||||
private const INCOMPATIBLE_PARAM_TYPE_PATTERN = '#Declaration of (?<current>\w.*?) should be compatible with (?<should_be>\w.*?)$#';
|
||||
|
||||
/**
|
||||
* @see https://regex101.com/r/D6Z5Uq/1/
|
||||
* @var string
|
||||
*/
|
||||
private const INCOMPATIBLE_RETURN_TYPE_PATTERN = '#Declaration of (?<current>\w.*?) must be compatible with (?<should_be>\w.*?)$#';
|
||||
|
||||
/**
|
||||
* @see https://regex101.com/r/RbJy9h/8/
|
||||
* @var string
|
||||
*/
|
||||
private const CLASS_METHOD_ARGUMENTS_PATTERN = '#(?<class>.*?)::(?<method>.*?)\((?<arguments>.*?)\)(:\s?(?<return_type>\w+))?#';
|
||||
|
||||
/**
|
||||
* @see https://regex101.com/r/RbJy9h/5
|
||||
* @var string
|
||||
*/
|
||||
private const ARGUMENTS_PATTERN = '#(\b(?<type>\w.*?)?\b )?\$(?<name>\w+)#sm';
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $paramChanges = [];
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $returnChanges = [];
|
||||
|
||||
/**
|
||||
* @param string[] $errors
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function processErrors(array $errors): array
|
||||
{
|
||||
$this->paramChanges = [];
|
||||
|
||||
foreach ($errors as $fatalError) {
|
||||
$match = Strings::match($fatalError, self::INCOMPATIBLE_PARAM_TYPE_PATTERN);
|
||||
if ($match) {
|
||||
$this->processIncompatibleParamTypeMatch($match);
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = Strings::match($fatalError, self::INCOMPATIBLE_RETURN_TYPE_PATTERN);
|
||||
if ($match) {
|
||||
$this->processIncompatibleReturnTypeMatch($match);
|
||||
}
|
||||
}
|
||||
|
||||
$config = [];
|
||||
if ($this->paramChanges !== []) {
|
||||
$config['services'][AddParamTypeDeclarationRector::class]['$typehintForParameterByMethodByClass'] = $this->paramChanges;
|
||||
}
|
||||
|
||||
if ($this->returnChanges !== []) {
|
||||
$config['services'][AddReturnTypeDeclarationRector::class]['$typehintForMethodByClass'] = $this->returnChanges;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function createScannedMethod(string $classMethodWithArgumentsDescription): ClassMethodWithArguments
|
||||
{
|
||||
$match = Strings::match($classMethodWithArgumentsDescription, self::CLASS_METHOD_ARGUMENTS_PATTERN);
|
||||
if (! $match) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
$arguments = $this->createArguments((string) $match['arguments']);
|
||||
|
||||
return new ClassMethodWithArguments($match['class'], $match['method'], $arguments, $match['return_type'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Argument[]
|
||||
*/
|
||||
private function createArguments(string $argumentsDescription): array
|
||||
{
|
||||
// 0 arguments
|
||||
if ($argumentsDescription === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$arguments = [];
|
||||
$argumentDescriptions = Strings::split($argumentsDescription, '#\b,\b#');
|
||||
foreach ($argumentDescriptions as $position => $argumentDescription) {
|
||||
$match = Strings::match((string) $argumentDescription, self::ARGUMENTS_PATTERN);
|
||||
if (! $match) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
$arguments[] = new Argument($match['name'], $position, $match['type'] ?? '');
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
private function collectClassMethodParamDifferences(
|
||||
ClassMethodWithArguments $scannedMethod,
|
||||
ClassMethodWithArguments $shouldBeMethod
|
||||
): void {
|
||||
foreach ($scannedMethod->getArguments() as $scannedMethodArgument) {
|
||||
$shouldBeArgument = $shouldBeMethod->getArgumentByPosition($scannedMethodArgument->getPosition());
|
||||
|
||||
if ($shouldBeArgument === null) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// types are identical, nothing to change
|
||||
if ($scannedMethodArgument->getType() === $shouldBeArgument->getType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->paramChanges[$scannedMethod->getClass()][$scannedMethod->getMethod()][$scannedMethodArgument->getPosition()] = $shouldBeArgument->getType();
|
||||
}
|
||||
}
|
||||
|
||||
private function processIncompatibleParamTypeMatch(array $match): void
|
||||
{
|
||||
if (! Strings::contains($match['current'], '::')) {
|
||||
// probably a function?
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
$scannedMethod = $this->createScannedMethod($match['current']);
|
||||
$shouldBeMethod = $this->createScannedMethod($match['should_be']);
|
||||
|
||||
$this->collectClassMethodParamDifferences($scannedMethod, $shouldBeMethod);
|
||||
}
|
||||
|
||||
private function processIncompatibleReturnTypeMatch(array $match): void
|
||||
{
|
||||
if (! Strings::contains($match['current'], '::')) {
|
||||
// probably a function?
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
$scannedMethod = $this->createScannedMethod($match['current']);
|
||||
$shouldBeMethod = $this->createScannedMethod($match['should_be']);
|
||||
|
||||
$this->collectClassMethodReturnDifferences($scannedMethod, $shouldBeMethod);
|
||||
}
|
||||
|
||||
private function collectClassMethodReturnDifferences(
|
||||
ClassMethodWithArguments $scannedMethod,
|
||||
ClassMethodWithArguments $shouldBeMethod
|
||||
): void {
|
||||
if ($scannedMethod->getReturnType() === $shouldBeMethod->getReturnType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->returnChanges[$scannedMethod->getClass()][$scannedMethod->getMethod()] = $shouldBeMethod->getReturnType();
|
||||
}
|
||||
}
|
45
src/ValueObject/Scan/Argument.php
Normal file
45
src/ValueObject/Scan/Argument.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\ValueObject\Scan;
|
||||
|
||||
final class Argument
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $position;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
public function __construct(string $name, int $position, string $type = '')
|
||||
{
|
||||
$this->position = $position;
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getPosition(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
75
src/ValueObject/Scan/ClassMethodWithArguments.php
Normal file
75
src/ValueObject/Scan/ClassMethodWithArguments.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\ValueObject\Scan;
|
||||
|
||||
final class ClassMethodWithArguments
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* @var Argument[]
|
||||
*/
|
||||
private $arguments = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $returnType;
|
||||
|
||||
/**
|
||||
* @param Argument[] $arguments
|
||||
*/
|
||||
public function __construct(string $class, string $method, array $arguments, string $returnType)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->method = $method;
|
||||
$this->arguments = $arguments;
|
||||
$this->returnType = $returnType;
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Argument[]
|
||||
*/
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getArgumentByPosition(int $position): ?Argument
|
||||
{
|
||||
foreach ($this->arguments as $argument) {
|
||||
if ($argument->getPosition() !== $position) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $argument;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getReturnType(): string
|
||||
{
|
||||
return $this->returnType;
|
||||
}
|
||||
}
|
22
src/Yaml/YamlPrinter.php
Normal file
22
src/Yaml/YamlPrinter.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Yaml;
|
||||
|
||||
use Nette\Utils\FileSystem;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
final class YamlPrinter
|
||||
{
|
||||
public function printYamlToString(array $yaml): string
|
||||
{
|
||||
return Yaml::dump($yaml, 10, 4, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||
}
|
||||
|
||||
public function printYamlToFile(array $yaml, string $targetFile): void
|
||||
{
|
||||
$yamlContent = $this->printYamlToString($yaml);
|
||||
FileSystem::write($targetFile, $yamlContent);
|
||||
}
|
||||
}
|
72
tests/Scan/ScannedErrorToRectorResolverTest.php
Normal file
72
tests/Scan/ScannedErrorToRectorResolverTest.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Scan;
|
||||
|
||||
use Rector\HttpKernel\RectorKernel;
|
||||
use Rector\Rector\ClassMethod\AddReturnTypeDeclarationRector;
|
||||
use Rector\Scan\ScannedErrorToRectorResolver;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeDeclarationRector;
|
||||
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
|
||||
|
||||
final class ScannedErrorToRectorResolverTest extends AbstractKernelTestCase
|
||||
{
|
||||
/**
|
||||
* @var ScannedErrorToRectorResolver
|
||||
*/
|
||||
private $scannedErrorToRectorResolver;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->bootKernel(RectorKernel::class);
|
||||
|
||||
$this->scannedErrorToRectorResolver = self::$container->get(ScannedErrorToRectorResolver::class);
|
||||
}
|
||||
|
||||
public function testParam(): void
|
||||
{
|
||||
$errors = [];
|
||||
$errors[] = 'Declaration of Kedlubna\extendTest::add($message) should be compatible with Kedlubna\test::add(string $message = \'\')';
|
||||
|
||||
$rectorConfiguration = $this->scannedErrorToRectorResolver->processErrors($errors);
|
||||
|
||||
$expectedConfiguration = [
|
||||
'services' => [
|
||||
AddParamTypeDeclarationRector::class => [
|
||||
'$typehintForParameterByMethodByClass' => [
|
||||
'Kedlubna\extendTest' => [
|
||||
'add' => [
|
||||
0 => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame($expectedConfiguration, $rectorConfiguration);
|
||||
}
|
||||
|
||||
public function testReturn(): void
|
||||
{
|
||||
$errors = [];
|
||||
$errors[] = 'Declaration of AAA\extendTest::nothing() must be compatible with AAA\test::nothing(): void;';
|
||||
|
||||
$rectorConfiguration = $this->scannedErrorToRectorResolver->processErrors($errors);
|
||||
|
||||
$expectedConfiguration = [
|
||||
'services' => [
|
||||
AddReturnTypeDeclarationRector::class => [
|
||||
'$typehintForMethodByClass' => [
|
||||
'AAA\extendTest' => [
|
||||
'nothing' => 'void',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame($expectedConfiguration, $rectorConfiguration);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user