mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-26 12:52:36 +00:00
remove scan-fatal-errors, move to migrify
This commit is contained in:
parent
a8ea0fb9a7
commit
b2f5f0ae17
19
.github/workflows/fatal_scan.yaml
vendored
19
.github/workflows/fatal_scan.yaml
vendored
|
@ -1,19 +0,0 @@
|
|||
name: Fatal Scan
|
||||
|
||||
on:
|
||||
pull_request: null
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
fatal_scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v1
|
||||
with:
|
||||
php-version: 7.3
|
||||
coverage: none # disable xdebug, pcov
|
||||
- run: composer install --no-progress --ansi
|
||||
- run: bin/rector scan-fatal-errors tests/Source/FatalErrors
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -18,6 +18,3 @@ create-rector.yaml
|
|||
/compiler/composer.lock
|
||||
/compiler/vendor
|
||||
/tmp
|
||||
|
||||
# from "scan-fatal-errors" command
|
||||
rector-types.yaml
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\Console\Command;
|
||||
|
||||
use Rector\Core\Configuration\Option;
|
||||
use Rector\Core\Scan\ErrorScanner;
|
||||
use Rector\Core\Scan\ScannedErrorToRectorResolver;
|
||||
use Rector\Core\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 ShellCode::SUCCESS;
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\Scan;
|
||||
|
||||
use Rector\Core\FileSystem\FilesFinder;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
final class ErrorScanner
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const COMMAND_LINE = 'include "vendor/autoload.php";';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $errors = [];
|
||||
|
||||
/**
|
||||
* @var FilesFinder
|
||||
*/
|
||||
private $filesFinder;
|
||||
|
||||
/**
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $symfonyStyle;
|
||||
|
||||
public function __construct(FilesFinder $filesFinder, SymfonyStyle $symfonyStyle)
|
||||
{
|
||||
$this->filesFinder = $filesFinder;
|
||||
$this->symfonyStyle = $symfonyStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $source
|
||||
* @return string[]
|
||||
*/
|
||||
public function scanSource(array $source): array
|
||||
{
|
||||
$this->setErrorHandler();
|
||||
|
||||
$fileInfos = $this->filesFinder->findInDirectoriesAndFiles($source, ['php']);
|
||||
|
||||
foreach ($fileInfos as $fileInfo) {
|
||||
$currentCommandLine = self::COMMAND_LINE . PHP_EOL;
|
||||
$currentCommandLine .= sprintf('include "%s";', $fileInfo->getRelativeFilePathFromCwd());
|
||||
|
||||
$currentCommandLine = sprintf("php -r '%s'", $currentCommandLine);
|
||||
|
||||
$this->symfonyStyle->note('Running PHP in sub-process: ' . $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();
|
||||
}
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\Scan;
|
||||
|
||||
use Nette\Utils\Strings;
|
||||
use Rector\Core\Exception\NotImplementedException;
|
||||
use Rector\Core\Rector\ClassMethod\AddReturnTypeDeclarationRector;
|
||||
use Rector\Core\ValueObject\Scan\Argument;
|
||||
use Rector\Core\ValueObject\Scan\ClassMethodWithArguments;
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeDeclarationRector;
|
||||
|
||||
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 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 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'] ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
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 collectClassMethodReturnDifferences(
|
||||
ClassMethodWithArguments $scannedMethod,
|
||||
ClassMethodWithArguments $shouldBeMethod
|
||||
): void {
|
||||
if ($scannedMethod->getReturnType() === $shouldBeMethod->getReturnType()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->returnChanges[$scannedMethod->getClass()][$scannedMethod->getMethod()] = $shouldBeMethod->getReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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($position, $match['type'] ?? '');
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\ValueObject\Scan;
|
||||
|
||||
final class Argument
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $position;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
public function __construct(int $position, string $type = '')
|
||||
{
|
||||
$this->position = $position;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getPosition(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\ValueObject\Scan;
|
||||
|
||||
final class ClassMethodWithArguments
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $returnType;
|
||||
|
||||
/**
|
||||
* @var Argument[]
|
||||
*/
|
||||
private $arguments = [];
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Core\Yaml;
|
||||
|
||||
use Nette\Utils\FileSystem;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
final class YamlPrinter
|
||||
|
@ -13,10 +12,4 @@ final class YamlPrinter
|
|||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\Tests\Scan;
|
||||
|
||||
use Rector\Core\HttpKernel\RectorKernel;
|
||||
use Rector\Core\Rector\ClassMethod\AddReturnTypeDeclarationRector;
|
||||
use Rector\Core\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