Skip to content

Commit

Permalink
Merge pull request #1054 from alessandroaussems/rector
Browse files Browse the repository at this point in the history
Add Rector Task
  • Loading branch information
veewee authored Nov 25, 2022
2 parents b8252a4 + f468f1c commit 2b327ae
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 1 deletion.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"phpstan/phpstan": "Lets GrumPHP discover bugs in your code without running it.",
"phpunit/phpunit": "Lets GrumPHP run your unit tests.",
"povils/phpmnd": "Lets GrumPHP help you detect magic numbers in PHP code.",
"rectorphp/rector ": "Lets GrumPHP instantly upgrade and automatically refactor your PHP code.",
"roave/security-advisories": "Lets GrumPHP be sure that there are no known security issues.",
"sebastian/phpcpd": "Lets GrumPHP find duplicated code.",
"squizlabs/php_codesniffer": "Lets GrumPHP sniff on your code.",
Expand Down
4 changes: 3 additions & 1 deletion doc/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ grumphp:
phpunitbridge: ~
phpversion: ~
progpilot: ~
psalm: ~
psalm: ~
rector: ~
robo: ~
securitychecker_enlightn: ~
securitychecker_local: ~
Expand Down Expand Up @@ -115,6 +116,7 @@ Every task has its own default configuration. It is possible to overwrite the pa
- [PhpVersion](tasks/phpversion.md)
- [Progpilot](tasks/progpilot.md)
- [Psalm](tasks/psalm.md)
- [Rector](tasks/rector.md)
- [Robo](tasks/robo.md)
- [Security Checker](tasks/securitychecker.md)
- [Enlightn](tasks/securitychecker/enlightn.md)
Expand Down
57 changes: 57 additions & 0 deletions doc/tasks/rector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Rector

Rector is a tool to instantly upgrade and automatically refactor your PHP 5.3+ code.
It lives under the `rector` namespace and has following configurable parameters:

## Composer
```bash
composer require --dev rectorphp/rector
```

## Config
```yaml
# grumphp.yml
grumphp:
tasks:
rector:
config: null
triggered_by: ['php']
ignore_patterns: []
clear_cache: true
no_diffs: false
```
**config**
*Default: null*
With this parameter you can specify the path your project's configuration file. When 'null' rector will run with the default file: rector.php
**triggered_by**
*Default: [php]*
This is a list of extensions to be sniffed.
**ignore_patterns**
*Default: []*
This is a list of patterns that will be ignored by Rector. With this option you can skip files like
tests. Leave this option blank to run Rector for every php file/directory specified in your
configuration.
**clear_cache**
*Default: true*
With this parameter you can run Rector without using the cache.
**no_diffs**
*Default: false*
With this parameter you can run Rector without showing file diffs.
7 changes: 7 additions & 0 deletions resources/config/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,13 @@ services:
tags:
- {name: grumphp.task, task: psalm}

GrumPHP\Task\Rector:
arguments:
- '@process_builder'
- '@formatter.raw_process'
tags:
- { name: grumphp.task, task: rector }

GrumPHP\Task\Robo:
arguments:
- '@process_builder'
Expand Down
86 changes: 86 additions & 0 deletions src/Task/Rector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace GrumPHP\Task;

use GrumPHP\Fixer\Provider\FixableProcessResultProvider;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Process\Process;

class Rector extends AbstractExternalTask
{
public static function getConfigurableOptions(): OptionsResolver
{
$resolver = new OptionsResolver();
$resolver->setDefaults([
'config' => null,
'triggered_by' => ['php'],
'ignore_patterns' => [],
'clear_cache' => true,
'no_diffs' => false,
]);

$resolver->addAllowedTypes('config', ['null', 'string']);
$resolver->addAllowedTypes('triggered_by', ['array']);
$resolver->addAllowedTypes('ignore_patterns', ['array']);
$resolver->addAllowedTypes('clear_cache', ['bool']);
$resolver->addAllowedTypes('no_diffs', ['bool']);

return $resolver;
}

public function canRunInContext(ContextInterface $context): bool
{
return $context instanceof RunContext || $context instanceof GitPreCommitContext;
}

public function run(ContextInterface $context): TaskResultInterface
{
$config = $this->getConfig()->getOptions();

$files = $context
->getFiles()
->notPaths($config['ignore_patterns'])
->extensions($config['triggered_by']);

if (0 === \count($files)) {
return TaskResult::createSkipped($this, $context);
}

$arguments = $this->processBuilder->createArgumentsForCommand('rector');
$arguments->add('process');
$arguments->add('--dry-run');
$arguments->add('--ansi');
$arguments->add('--no-progress-bar');

$arguments->addOptionalArgument('--config=%s', $config['config']);
$arguments->addOptionalArgument('--clear-cache', $config['clear_cache']);
$arguments->addOptionalArgument('--no-diffs', $config['no_diffs']);

if ($context instanceof GitPreCommitContext) {
$arguments->addFiles($files);
}

$process = $this->processBuilder->buildProcess($arguments);
$process->run();

if (!$process->isSuccessful()) {
return FixableProcessResultProvider::provide(
TaskResult::createFailed($this, $context, $this->formatter->format($process)),
function () use ($arguments): Process {
$arguments->removeElement('--dry-run');

return $this->processBuilder->buildProcess($arguments);
}
);
}

return TaskResult::createPassed($this, $context);
}
}
171 changes: 171 additions & 0 deletions test/Unit/Task/RectorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php

declare(strict_types=1);

namespace GrumPHPTest\Unit\Task;

use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Formatter\RawProcessFormatter;
use GrumPHP\Runner\FixableTaskResult;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
use GrumPHP\Task\Rector;
use GrumPHP\Task\TaskInterface;
use GrumPHP\Test\Task\AbstractExternalTaskTestCase;
use Prophecy\Prophecy\ObjectProphecy;

class RectorTest extends AbstractExternalTaskTestCase
{
/**
* @var ProcessFormatterInterface|ObjectProphecy
*/
protected $formatter;

protected function provideTask(): TaskInterface
{
$this->formatter = $this->prophesize(RawProcessFormatter::class);

return new Rector(
$this->processBuilder->reveal(),
$this->formatter->reveal()
);
}

public function provideConfigurableOptions(): iterable
{
yield 'defaults' => [
[],
[
'config' => null,
'triggered_by' => ['php'],
'ignore_patterns' => [],
'clear_cache' => true,
'no_diffs' => false,
]
];
}

public function provideRunContexts(): iterable
{
yield 'run-context' => [
true,
$this->mockContext(RunContext::class)
];

yield 'pre-commit-context' => [
true,
$this->mockContext(GitPreCommitContext::class)
];

yield 'other' => [
false,
$this->mockContext()
];
}

public function provideFailsOnStuff(): iterable
{
yield 'exitCode1' => [
[],
$this->mockContext(RunContext::class, ['hello.php']),
function () {
$this->mockProcessBuilder('rector', $process = $this->mockProcess(1));

$this->formatter->format($process)->willReturn($message = 'message');
},
'message',
FixableTaskResult::class
];
}

public function providePassesOnStuff(): iterable
{
yield 'exitCode0' => [
[],
$this->mockContext(RunContext::class, ['hello.php']),
function () {
$this->mockProcessBuilder('rector', $this->mockProcess(0));
}
];
}

public function provideSkipsOnStuff(): iterable
{
yield 'no-files' => [
[],
$this->mockContext(RunContext::class),
function () {}
];
yield 'no-files-after-triggered-by' => [
[],
$this->mockContext(RunContext::class, ['notaphpfile.txt']),
function () {}
];
yield 'no-files-after-ignore-patterns' => [
[
'ignore_patterns' => ['test/'],
],
$this->mockContext(RunContext::class, ['test/file.php']),
function () {}
];
}

public function provideExternalTaskRuns(): iterable
{
yield 'defaults' => [
[],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'rector',
[
'process',
'--dry-run',
'--ansi',
'--no-progress-bar',
'--clear-cache',
]
];
yield 'config' => [
[
'config' => 'rector-config.php',
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'rector',
[
'process',
'--dry-run',
'--ansi',
'--no-progress-bar',
'--config=rector-config.php',
'--clear-cache',
]
];
yield 'no-clear-cache' => [
[
'clear_cache' => false,
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'rector',
[
'process',
'--dry-run',
'--ansi',
'--no-progress-bar',
]
];
yield 'no-diffs' => [
[
'no_diffs' => true,
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'rector',
[
'process',
'--dry-run',
'--ansi',
'--no-progress-bar',
'--clear-cache',
'--no-diffs'
]
];
}
}

0 comments on commit 2b327ae

Please sign in to comment.