Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 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 All @@ -37,7 +37,7 @@
"symplify/set-config-resolver": "^8.1.15",
"symplify/smart-file-system": "^8.1.15",
"tracy/tracy": "^2.7",
"phpstan/phpstan": "^0.12.33"
"phpstan/phpstan": "^0.12.34"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16",
Expand All @@ -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);
};
36 changes: 36 additions & 0 deletions docs/nodes_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,25 @@ list($someVariable)
* `$items` - `/** @var (ArrayItem|null)[] List of items to assign to */`
<br>

### `PhpParser\Node\Expr\Match_`

* requires arguments on construct


#### Example PHP Code

```php
match ($variable) {
1 => 'yes',
}
```

#### Public Properties

* `$cond` - `/** @var Node\Expr */`
* `$arms` - `/** @var MatchArm[] */`
<br>

### `PhpParser\Node\Expr\MethodCall`

* requires arguments on construct
Expand Down Expand Up @@ -2509,6 +2528,23 @@ identifier
* `$specialClassNames` - ``
<br>

### `PhpParser\Node\MatchArm`

* requires arguments on construct


#### Example PHP Code

```php
1 => 'yes'
```

#### Public Properties

* `$conds` - `/** @var null|Node\Expr[] */`
* `$body` - `/** @var Node\Expr */`
<br>

### `PhpParser\Node\NullableType`

* requires arguments on construct
Expand Down
45 changes: 43 additions & 2 deletions docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# All 541 Rectors Overview
# All 542 Rectors Overview

- [Projects](#projects)
---
Expand Down Expand Up @@ -54,7 +54,7 @@
- [Php72](#php72) (11)
- [Php73](#php73) (10)
- [Php74](#php74) (15)
- [Php80](#php80) (11)
- [Php80](#php80) (12)
- [PhpDeglobalize](#phpdeglobalize) (1)
- [PhpSpecToPHPUnit](#phpspectophpunit) (7)
- [Polyfill](#polyfill) (2)
Expand Down Expand Up @@ -10559,6 +10559,47 @@ Change annotation to attribute

<br><br>

### `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`

- class: [`Rector\Php80\Rector\FuncCall\ClassOnObjectRector`](/../master/rules/php80/src/Rector/FuncCall/ClassOnObjectRector.php)
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(.*?)#'
195 changes: 195 additions & 0 deletions rules/php80/src/Rector/Switch_/ChangeSwitchToMatchRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php

declare(strict_types=1);

namespace Rector\Php80\Rector\Switch_;

use PhpParser\Node;
use PhpParser\Node\Expr;
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 Expr|null
*/
private $assignExpr;

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->assignExpr = 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->assignExpr) {
return new Assign($this->assignExpr, $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->assignExpr = $expr->var;
$expr = $expr->expr;
}

$condList = $case->cond === null ? null : [$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;
}
}
Loading