remove scan-fatal-errors, move to migrify

This commit is contained in:
TomasVotruba 2020-05-03 00:33:48 +02:00
parent a8ea0fb9a7
commit b2f5f0ae17
9 changed files with 0 additions and 612 deletions

View File

@ -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
View File

@ -18,6 +18,3 @@ create-rector.yaml
/compiler/composer.lock
/compiler/vendor
/tmp
# from "scan-fatal-errors" command
rector-types.yaml

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}