Skip to content

Commit

Permalink
[PHP 8.0] Add ChangeSwitchToMatchRector
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Jul 30, 2020
1 parent 10e024b commit eec13d9
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 3 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/conflict_phpstan_rector_php_parser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# see https://github.com/rectorphp/rector/pull/3788#issue-456723123
name: PHP-Parser conflict

on:
push:
branches:
- master

jobs:
code_coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v1
with:
php-version: 7.4
coverage: none

- run: composer install --no-progress --ansi

# https://kizu514.com/blog/pcov-is-better-than-phpdbg-and-xdebug-for-code-coverage/
- run: bin/rector
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
"jean85/pretty-package-versions": "^1.2",
"nette/robot-loader": "^3.2",
"nette/utils": "^3.1",
"nikic/php-parser": "4.6",
"nikic/php-parser": "^4.7",
"ondram/ci-detector": "^3.4",
"phpstan/phpdoc-parser": "^0.4.7",
"phpstan/phpdoc-parser": "^0.4.8",
"phpstan/phpstan-phpunit": "^0.12.10",
"psr/simple-cache": "^1.0",
"sebastian/diff": "^3.0|^4.0",
Expand Down Expand Up @@ -55,6 +55,9 @@
"symplify/phpstan-extensions": "^8.1.15",
"thecodingmachine/phpstan-strict-rules": "^0.12"
},
"replace": {
"rector/rector-prefixed": "self.version"
},
"autoload": {
"psr-4": {
"Rector\\Architecture\\": "rules/architecture/src",
Expand Down
3 changes: 3 additions & 0 deletions config/set/php80.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Rector\Php80\Rector\Identical\StrEndsWithRector;
use Rector\Php80\Rector\Identical\StrStartsWithRector;
use Rector\Php80\Rector\NotIdentical\StrContainsRector;
use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector;
use Rector\Php80\Rector\Ternary\GetDebugTypeRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

Expand Down Expand Up @@ -39,4 +40,6 @@
$services->set(RemoveUnusedVariableInCatchRector::class);

$services->set(ClassPropertyAssignToConstructorPromotionRector::class);

$services->set(ChangeSwitchToMatchRector::class);
};
3 changes: 3 additions & 0 deletions docs/nodes_overview.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<<<<<<< HEAD
# Node Overview

* [Expressions](#expressions)
Expand Down Expand Up @@ -2562,3 +2563,5 @@ string|null
* `$types` - `/** @var (Identifier|Name)[] Types */`
<br>

=======
>>>>>>> [PHP 8.0] Add ChangeSwitchToMatchRector
49 changes: 49 additions & 0 deletions docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<<<<<<< HEAD
# All 541 Rectors Overview
=======
# All 534 Rectors Overview
>>>>>>> [PHP 8.0] Add ChangeSwitchToMatchRector
- [Projects](#projects)
---
Expand Down Expand Up @@ -9492,7 +9496,52 @@ Changes if/else to spaceship <=> where useful

<br><br>

<<<<<<< HEAD
### `MultiDirnameRector`
=======
### `ChangeSwitchToMatchRector`

- class: [`Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector`](/../master/rules/php80/src/Rector/Switch_/ChangeSwitchToMatchRector.php)
- [test fixtures](/../master/rules/php80/tests/Rector/Switch_/ChangeSwitchToMatchRector/Fixture)

Change `switch()` to `match()`

```diff
class SomeClass
{
public function run()
{
- $statement = switch ($this->lexer->lookahead['type']) {
- case Lexer::T_SELECT:
- $statement = $this->SelectStatement();
- break;
-
- case Lexer::T_UPDATE:
- $statement = $this->UpdateStatement();
- break;
-
- case Lexer::T_DELETE:
- $statement = $this->DeleteStatement();
- break;
-
- default:
- $this->syntaxError('SELECT, UPDATE or DELETE');
- break;
- }
+ $statement = match ($this->lexer->lookahead['type']) {
+ Lexer::T_SELECT => $this->SelectStatement(),
+ Lexer::T_UPDATE => $this->UpdateStatement(),
+ Lexer::T_DELETE => $this->DeleteStatement(),
+ default => $this->syntaxError('SELECT, UPDATE or DELETE'),
+ };
}
}
```

<br><br>

### `ClassOnObjectRector`
>>>>>>> [PHP 8.0] Add ChangeSwitchToMatchRector
- class: [`Rector\Php70\Rector\FuncCall\MultiDirnameRector`](/../master/rules/php70/src/Rector/FuncCall/MultiDirnameRector.php)
- [test fixtures](/../master/rules/php70/tests/Rector/FuncCall/MultiDirnameRector/Fixture)
Expand Down
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ parameters:

- '#Method (.*?) specified in iterable type Symfony\\Component\\Process\\Process#'
- '#Cannot cast PhpParser\\Node\\Expr\\Error\|PhpParser\\Node\\Identifier to string#'
- '#Class cognitive complexity for "DumpNodesCommand" is 103, keep it under 50#'
- '#Class cognitive complexity for "DumpNodesCommand" is \d+, keep it under 50#'
- '#Cognitive complexity for "Rector\\Utils\\DocumentationGenerator\\Command\\DumpNodesCommand\:\:execute\(\)" is \d+, keep it under 9#'

- '#Method Rector\\Utils\\DocumentationGenerator\\Node\\NodeClassProvider\:\:getNodeClasses\(\) should return array<class\-string\> but returns array<int, \(int\|string\)\>#'
Expand Down Expand Up @@ -379,3 +379,4 @@ parameters:
- '#Static property Symplify\\PackageBuilder\\Tests\\AbstractKernelTestCase\:\:\$container \(Psr\\Container\\ContainerInterface\) does not accept Rector\\Naming\\Tests\\Rector\\Class_\\RenamePropertyToMatchTypeRector\\Source\\ContainerInterface\|Symfony\\Component\\DependencyInjection\\Container#'
- '#Static property Rector\\Core\\Testing\\PHPUnit\\AbstractGenericRectorTestCase\:\:\$allRectorContainer \(Rector\\Naming\\Tests\\Rector\\Class_\\RenamePropertyToMatchTypeRector\\Source\\ContainerInterface\|Symfony\\Component\\DependencyInjection\\Container\|null\) does not accept Psr\\Container\\ContainerInterface#'
- '#Separate function "Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ref\(\)" in method call to standalone row to improve readability#'
- '#Class with base "(.*?)" name is already used in "_HumbugBox(.*?)#'
199 changes: 199 additions & 0 deletions rules/php80/src/Rector/Switch_/ChangeSwitchToMatchRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?php

declare(strict_types=1);

namespace Rector\Php80\Rector\Switch_;

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Match_;
use PhpParser\Node\MatchArm;
use PhpParser\Node\Stmt\Break_;
use PhpParser\Node\Stmt\Case_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Switch_;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;

/**
* @see https://wiki.php.net/rfc/match_expression_v2
*
* @see \Rector\Php80\Tests\Rector\Switch_\ChangeSwitchToMatchRector\ChangeSwitchToMatchRectorTest
*/
final class ChangeSwitchToMatchRector extends AbstractRector
{
/**
* @var Node\Expr|null
*/
private $assignVariable;

public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change switch() to match()', [
new CodeSample(
<<<'PHP'
class SomeClass
{
public function run()
{
$statement = switch ($this->lexer->lookahead['type']) {
case Lexer::T_SELECT:
$statement = $this->SelectStatement();
break;
case Lexer::T_UPDATE:
$statement = $this->UpdateStatement();
break;
case Lexer::T_DELETE:
$statement = $this->DeleteStatement();
break;
default:
$this->syntaxError('SELECT, UPDATE or DELETE');
break;
}
}
}
PHP
,
<<<'PHP'
class SomeClass
{
public function run()
{
$statement = match ($this->lexer->lookahead['type']) {
Lexer::T_SELECT => $this->SelectStatement(),
Lexer::T_UPDATE => $this->UpdateStatement(),
Lexer::T_DELETE => $this->DeleteStatement(),
default => $this->syntaxError('SELECT, UPDATE or DELETE'),
};
}
}
PHP

),
]);
}

/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Switch_::class];
}

/**
* @param Switch_ $node
*/
public function refactor(Node $node): ?Node
{
$this->assignVariable = null;

if (! $this->hasEachCaseBreak($node)) {
return null;
}

if (! $this->hasSingleStmtCases($node)) {
return null;
}

if (! $this->hasSingleAssignVariableInStmtCase($node)) {
return null;
}

$matchArms = $this->createMatchArmsFromCases($node->cases);
$match = new Match_($node->cond, $matchArms);
if ($this->assignVariable) {
return new Assign($this->assignVariable, $match);
}

return $match;
}

private function hasEachCaseBreak(Switch_ $switch): bool
{
foreach ($switch->cases as $case) {
foreach ($case->stmts as $caseStmt) {
if (! $caseStmt instanceof Break_) {
continue;
}

return true;
}
}

return false;
}

/**
* @param Case_[] $cases
* @return MatchArm[]
*/
private function createMatchArmsFromCases(array $cases): array
{
$matchArms = [];
foreach ($cases as $case) {
$stmt = $case->stmts[0];
if (! $stmt instanceof Expression) {
throw new ShouldNotHappenException();
}

$expr = $stmt->expr;

if ($expr instanceof Assign) {
$this->assignVariable = $expr->var;
$expr = $expr->expr;
}

if ($case->cond === null) {
$condList = null;
} else {
$condList = [$case->cond];
}

$matchArms[] = new MatchArm($condList, $expr);
}

return $matchArms;
}

private function hasSingleStmtCases(Switch_ $switch): bool
{
foreach ($switch->cases as $case) {
$stmtsWithoutBreak = array_filter($case->stmts, function (Node $node) {
return ! $node instanceof Break_;
});

if (count($stmtsWithoutBreak) !== 1) {
return false;
}
}

return true;
}

private function hasSingleAssignVariableInStmtCase(Switch_ $switch): bool
{
$assignVariableNames = [];

foreach ($switch->cases as $case) {
/** @var Expression $onlyStmt */
$onlyStmt = $case->stmts[0];
$expr = $onlyStmt->expr;

if (! $expr instanceof Assign) {
continue;
}

$assignVariableNames[] = $this->getName($expr->var);
}

$assignVariableNames = array_unique($assignVariableNames);

return count($assignVariableNames) <= 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Rector\Php80\Tests\Rector\Switch_\ChangeSwitchToMatchRector;

use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector;
use Symplify\SmartFileSystem\SmartFileInfo;

final class ChangeSwitchToMatchRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}

public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

protected function getRectorClass(): string
{
return ChangeSwitchToMatchRector::class;
}
}
Loading

0 comments on commit eec13d9

Please sign in to comment.