Skip to content

Commit

Permalink
Merge branch 'main' into feature/livewire-computed-property
Browse files Browse the repository at this point in the history
  • Loading branch information
GeniJaho authored Jul 31, 2024
2 parents 4a74d77 + 4e98afe commit f179725
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 13 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Rector Rules for Laravel

[![Tests](https://github.com/driftingly/rector-laravel/actions/workflows/tests.yaml/badge.svg)](https://github.com/driftingly/rector-laravel/actions/workflows/tests.yaml)
[![Code Analysis](https://github.com/driftingly/rector-laravel/actions/workflows/code_analysis.yaml/badge.svg)](https://github.com/driftingly/rector-laravel/actions/workflows/code_analysis.yaml)
[![Packagist Downloads](https://img.shields.io/packagist/dm/driftingly/rector-laravel)](https://packagist.org/packages/driftingly/rector-laravel/stats)
Expand All @@ -12,10 +13,10 @@ This package is a [Rector](https://github.com/rectorphp/rector) extension develo

Rules for additional first party packages are included as well e.g. Cashier and Livewire.

Install the `RectorLaravel` package as dependency:
Install as a dev dependency:

```bash
composer require driftingly/rector-laravel --dev
composer require --dev driftingly/rector-laravel
```

## Use Sets
Expand All @@ -39,15 +40,16 @@ return RectorConfig::configure()

| Set | Purpose |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [LaravelSetList::LARAVEL_ARRAYACCESS_TO_METHOD_CALL](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-arrayaccess-to-method-call.php) | Converts uses of things like `$app['config']` to `$app->make('config')`. |
| [LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-array-str-functions-to-static-call.php) | Converts most string and array helpers into Str and Arr Facades' static calls.<br/>https://laravel.com/docs/11.x/facades#facades-vs-helper-functions |
| [LaravelSetList::LARAVEL_CODE_QUALITY](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-code-quality.php) | Replaces magical call on `$this->app["something"]` to standalone variable with PHPDocs. |
| [LaravelSetList::LARAVEL_COLLECTION](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-collection.php) | Improves the usage of Laravel Collections by using simpler, more efficient, or more readable methods. |
| [LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-container-string-to-fully-qualified-name.php) | Changes the string or class const used for a service container make call.<br/>https://laravel.com/docs/11.x/container#the-make-method |
| [LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-eloquent-magic-method-to-query-builder.php) | Transforms magic method calls on Eloquent Models into corresponding Query Builder method calls.<br/>https://laravel.com/docs/11.x/eloquent |
| [LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-facade-aliases-to-full-names.php) | Replaces Facade aliases with full Facade names.<br/>https://laravel.com/docs/11.x/facades#facade-class-reference |
| [LaravelSetList::LARAVEL_IF_HELPERS](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-if-helpers.php) | Replaces `abort()`, `report()`, `throw` statements inside conditions with `abort_if()`, `report_if()`, `throw_if()` function calls.<br/>https://laravel.com/docs/11.x/helpers#method-abort-if |
| [LaravelSetList::LARAVEL_LEGACY_FACTORIES_TO_CLASSES](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-legacy-factories-to-classes.php) | Migrates Eloquent legacy model factories (with closures) into class based factories.<br/>https://laravel.com/docs/8.x/releases#model-factory-classes |
| [LaravelSetList::LARAVEL_STATIC_TO_INJECTION](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-static-to-injection.php) | Replaces Laravel's Facades with Dependency Injection.<br/>https://tomasvotruba.com/blog/2019/03/04/how-to-turn-laravel-from-static-to-dependency-injection-in-one-day/<br/>https://laravel.com/docs/11.x/facades#facades-vs-dependency-injection |
| [LaravelSetList::LARAVEL_ARRAYACCESS_TO_METHOD_CALL](https://github.com/driftingly/rector-laravel/blob/main/config/sets/laravel-arrayaccess-to-method-call.php) | Converts uses of things like `$app['config']` to `$app->make('config')`. |

## Contributors

Expand Down
4 changes: 1 addition & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@
"fix": "vendor/bin/duster fix",
"rector-dry-run": "vendor/bin/rector process --dry-run --ansi",
"rector": "vendor/bin/rector process --ansi",
"docs": [
"vendor/bin/rule-doc-generator generate src --output-file docs/rector_rules_overview.md --ansi"
]
"docs": "vendor/bin/rule-doc-generator generate src --output-file docs/rector_rules_overview.md --ansi"
},
"minimum-stability": "dev",
"prefer-stable": true,
Expand Down
11 changes: 11 additions & 0 deletions config/sets/laravel-collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use RectorLaravel\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->import(__DIR__ . '/../config.php');
$rectorConfig->rule(AvoidNegatedCollectionFilterOrRejectRector::class);
};
20 changes: 20 additions & 0 deletions docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,26 @@ Replace `(new \Illuminate\Testing\TestResponse)->assertStatus(200)` with `(new \

<br>

## AvoidNegatedCollectionFilterOrRejectRector

Avoid negated conditionals in `filter()` by using `reject()`, or vice versa.

- class: [`RectorLaravel\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector`](../src/Rector/MethodCall/AvoidNegatedCollectionFilterOrRejectRector.php)

```diff
use Illuminate\Support\Collection;

$collection = new Collection([0, 1, null, -1]);
-$collection->filter(fn (?int $number): bool => ! is_null($number));
-$collection->filter(fn (?int $number): bool => ! $number);
-$collection->reject(fn (?int $number) => ! $number > 0);
+$collection->reject(fn (?int $number): bool => is_null($number)); // Avoid negation
+$collection->reject(fn (?int $number): bool => (bool) $number); // Ensures explicit cast
+$collection->filter(fn (?int $number): bool => $number > 0); // Adds return type
```

<br>

## CallOnAppArrayAccessToStandaloneAssignRector

Replace magical call on `$this->app["something"]` to standalone type assign variable
Expand Down
111 changes: 111 additions & 0 deletions src/Rector/MethodCall/AvoidNegatedCollectionFilterOrRejectRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace RectorLaravel\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\Cast\Bool_;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PHPStan\Type\ObjectType;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \RectorLaravel\Tests\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector\AvoidNegatedCollectionFilterOrRejectRectorTest
*/
final class AvoidNegatedCollectionFilterOrRejectRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Avoid negated conditionals in `filter()` by using `reject()`, or vice versa.',
[
new CodeSample(
<<<'CODE_SAMPLE'
use Illuminate\Support\Collection;
$collection = new Collection([0, 1, null, -1]);
$collection->filter(fn (?int $number): bool => ! is_null($number));
$collection->filter(fn (?int $number): bool => ! $number);
$collection->reject(fn (?int $number) => ! $number > 0);
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
use Illuminate\Support\Collection;
$collection = new Collection([0, 1, null, -1]);
$collection->reject(fn (?int $number): bool => is_null($number)); // Avoid negation
$collection->reject(fn (?int $number): bool => (bool) $number); // Explicitly cast
$collection->filter(fn (?int $number): bool => $number > 0); // Adds return type
CODE_SAMPLE
),
]
);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
}

/**
* @param MethodCall $node
*/
public function refactor(Node $node): ?Node
{
return $this->updateFilterOrRejectCall($node);
}

private function updateFilterOrRejectCall(MethodCall $methodCall): ?MethodCall
{
if (! $this->isObjectType($methodCall->var, new ObjectType('Illuminate\Support\Enumerable'))) {
return null;
}

if (! $this->isNames($methodCall->name, ['filter', 'reject'])) {
return null;
}

$args = $methodCall->getArgs();
if (count($args) !== 1) {
return null;
}

$arg = $args[0];
$argValue = $arg->value;

if (! $argValue instanceof ArrowFunction) {
return null;
}

$return = $argValue->expr;
if (! $return instanceof BooleanNot) {
return null;
}

$methodCall->name = new Identifier(
$this->isName($methodCall->name, 'filter')
? 'reject'
: 'filter'
);

// Since negation implicitly casts to boolean, we need to replace it with an
// explicit cast - unless the value is already a boolean.
$returnExpr = $return->expr;
$argValue->expr = $this->getType($returnExpr)->isBoolean()->yes()
? $returnExpr
: new Bool_($returnExpr);

$argValue->returnType = new Identifier('bool');

return $methodCall;
}
}
19 changes: 12 additions & 7 deletions src/Set/LaravelSetList.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,27 +91,27 @@ final class LaravelSetList implements SetListInterface
/**
* @var string
*/
final public const LARAVEL_STATIC_TO_INJECTION = __DIR__ . '/../../config/sets/laravel-static-to-injection.php';
final public const LARAVEL_ARRAYACCESS_TO_METHOD_CALL = __DIR__ . '/../../config/sets/laravel-arrayaccess-to-method-call.php';

/**
* @var string
*/
final public const LARAVEL_CODE_QUALITY = __DIR__ . '/../../config/sets/laravel-code-quality.php';
final public const LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL = __DIR__ . '/../../config/sets/laravel-array-str-functions-to-static-call.php';

/**
* @var string
*/
final public const LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL = __DIR__ . '/../../config/sets/laravel-array-str-functions-to-static-call.php';
final public const LARAVEL_CODE_QUALITY = __DIR__ . '/../../config/sets/laravel-code-quality.php';

/**
* @var string
*/
final public const LARAVEL_LEGACY_FACTORIES_TO_CLASSES = __DIR__ . '/../../config/sets/laravel-legacy-factories-to-classes.php';
final public const LARAVEL_COLLECTION = __DIR__ . '/../../config/sets/laravel-collection.php';

/**
* @var string
*/
final public const LARAVEL_FACADE_ALIASES_TO_FULL_NAMES = __DIR__ . '/../../config/sets/laravel-facade-aliases-to-full-names.php';
final public const LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME = __DIR__ . '/../../config/sets/laravel-container-string-to-fully-qualified-name.php';

/**
* @var string
Expand All @@ -121,7 +121,7 @@ final class LaravelSetList implements SetListInterface
/**
* @var string
*/
final public const LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME = __DIR__ . '/../../config/sets/laravel-container-string-to-fully-qualified-name.php';
final public const LARAVEL_FACADE_ALIASES_TO_FULL_NAMES = __DIR__ . '/../../config/sets/laravel-facade-aliases-to-full-names.php';

/**
* @var string
Expand All @@ -131,5 +131,10 @@ final class LaravelSetList implements SetListInterface
/**
* @var string
*/
final public const LARAVEL_ARRAYACCESS_TO_METHOD_CALL = __DIR__ . '/../../config/sets/laravel-arrayaccess-to-method-call.php';
final public const LARAVEL_LEGACY_FACTORIES_TO_CLASSES = __DIR__ . '/../../config/sets/laravel-legacy-factories-to-classes.php';

/**
* @var string
*/
final public const LARAVEL_STATIC_TO_INJECTION = __DIR__ . '/../../config/sets/laravel-static-to-injection.php';
}
20 changes: 20 additions & 0 deletions stubs/Illuminate/Support/Collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Illuminate\Support;

if (class_exists('Illuminate\Support\Collection')) {
return;
}

/**
* @template TKey of array-key
*
* @template-covariant TValue
*
* @implements \Illuminate\Support\Enumerable<TKey, TValue>
*/
class Collection implements Enumerable
{
}
23 changes: 23 additions & 0 deletions stubs/Illuminate/Support/Enumerable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@
return;
}

/**
* @template TKey of array-key
*
* @template-covariant TValue
*
* @extends \Illuminate\Contracts\Support\Arrayable<TKey, TValue>
* @extends \IteratorAggregate<TKey, TValue>
*/
interface Enumerable
{
/**
* Run a filter over each of the items.
*
* @param (callable(TValue): bool)|null $callback
* @return static
*/
public function filter(?callable $callback = null);

/**
* Create a collection of all elements that do not pass a given truth test.
*
* @param (callable(TValue, TKey): bool)|bool|TValue $callback
* @return static
*/
public function reject($callback = true);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace RectorLaravel\Tests\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class AvoidNegatedCollectionFilterOrRejectRectorTest extends AbstractRectorTestCase
{
public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

/**
* @test
*/
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector\Fixture;

use Illuminate\Support\Collection;

(new Collection([0, 1, null, -1]))
->filter(fn (?int $number): bool => ! $number);

(new Collection([0, 1, null, -1]))
->reject(fn (?int $number): bool => ! $number);

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector\Fixture;

use Illuminate\Support\Collection;

(new Collection([0, 1, null, -1]))
->reject(fn (?int $number): bool => (bool) $number);

(new Collection([0, 1, null, -1]))
->filter(fn (?int $number): bool => (bool) $number);

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector\Fixture;

use Illuminate\Support\Collection;

(new Collection([0, 1, null, -1]))
->filter(fn (?int $number) => ! is_null($number));

(new Collection([0, 1, null, -1]))
->reject(fn (?int $number) => ! is_null($number));

?>
-----
<?php

namespace RectorLaravel\Tests\Rector\MethodCall\AvoidNegatedCollectionFilterOrRejectRector\Fixture;

use Illuminate\Support\Collection;

(new Collection([0, 1, null, -1]))
->reject(fn (?int $number): bool => is_null($number));

(new Collection([0, 1, null, -1]))
->filter(fn (?int $number): bool => is_null($number));

?>
Loading

0 comments on commit f179725

Please sign in to comment.