Skip to content
Open
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
16 changes: 15 additions & 1 deletion docs/rules.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 60 Rules Overview
# 61 Rules Overview

## ChainExpectCallsRector

Expand Down Expand Up @@ -30,6 +30,20 @@ Chains consecutive `expect()` calls into a single chained expectation, combining

<br>

## ConvertAndToExpectRector

Splits `->and()` calls in `expect()` chains into separate `expect()` statements

- class: [`Pest\Rector\Rules\ConvertAndToExpectRector`](../src/Rules/ConvertAndToExpectRector.php)

```diff
-expect($a)->toBe(10)->and($b)->toBe(20);
+expect($a)->toBe(10);
+expect($b)->toBe(20);
```

<br>

## ConvertAssertToExpectRector

Converts `$this->assert*()` calls to Pest `expect()` chains
Expand Down
38 changes: 38 additions & 0 deletions src/AbstractRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\While_;
use PhpParser\Node\VariadicPlaceholder;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpParser\Enum\NodeGroup;
use Rector\PhpParser\Node\FileNode;
use Rector\Rector\AbstractRector as BaseAbstractRector;
Expand Down Expand Up @@ -281,6 +282,43 @@ protected function rebuildMethodChain(Expr $base, array $methods): Expr
return $result;
}

protected function applyNewlineAttributes(Expr $chain): void
{
if (! defined(AttributeKey::class.'::NEWLINE_ON_FLUENT_CALL')) {
return;
}

$current = $chain;

while ($current instanceof MethodCall) {
$var = $current->var;

if ($var instanceof FuncCall) {
break;
}

if ($var instanceof PropertyFetch) {
$current = $var->var;

continue;
}

if (! $var instanceof MethodCall) {
break;
}

if ($this->isName($var->name, 'and')) {
$var->setAttribute(AttributeKey::NEWLINE_ON_FLUENT_CALL, true);
$current = $var->var;

continue;
}

$current->setAttribute(AttributeKey::NEWLINE_ON_FLUENT_CALL, true);
$current = $var;
}
}

/**
* @param array<Node> $sources
*/
Expand Down
38 changes: 0 additions & 38 deletions src/Rules/ChainExpectCallsRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\NullsafePropertyFetch;
use PhpParser\Node\Expr\PropertyFetch;
Expand Down Expand Up @@ -420,41 +419,4 @@ private function mergeDifferentVariableChains(array &$stmts, int $key): bool

return true;
}

private function applyNewlineAttributes(Expr $chain): void
{
if (! defined(AttributeKey::class.'::NEWLINE_ON_FLUENT_CALL')) {
return;
}

$current = $chain;

while ($current instanceof MethodCall) {
$var = $current->var;

if ($var instanceof FuncCall) {
break;
}

if ($var instanceof PropertyFetch) {
$current = $var->var;

continue;
}

if (! $var instanceof MethodCall) {
break;
}

if ($this->isName($var->name, 'and')) {
$var->setAttribute(AttributeKey::NEWLINE_ON_FLUENT_CALL, true);
$current = $var->var;

continue;
}

$current->setAttribute(AttributeKey::NEWLINE_ON_FLUENT_CALL, true);
$current = $var;
}
}
}
119 changes: 119 additions & 0 deletions src/Rules/ConvertAndToExpectRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

declare(strict_types=1);

namespace Pest\Rector\Rules;

use Pest\Rector\AbstractRector;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Expression;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class ConvertAndToExpectRector extends AbstractRector
{
// @codeCoverageIgnoreStart
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Splits `->and()` calls in expect() chains into separate expect() statements',
[
new CodeSample(
<<<'CODE_SAMPLE'
expect($a)->toBe(10)->and($b)->toBe(20);
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
expect($a)->toBe(10);
expect($b)->toBe(20);
CODE_SAMPLE
),
]
);
}

// @codeCoverageIgnoreEnd

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

/**
* @param Expression $node
* @return array<Expression>|null
*/
public function refactor(Node $node): ?array
{
if (! $node->expr instanceof MethodCall) {
return null;
}

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

$segments = [];
$segmentOutermost = $node->expr;
$current = $node->expr;
$child = null;

while ($current instanceof MethodCall || $current instanceof PropertyFetch) {
if (! $current instanceof MethodCall || ! $this->isName($current->name, 'and')) {
$child = $current;
$current = $current->var;

continue;
}

if (! isset($current->args[0]) || ! $current->args[0] instanceof Arg) {
return null;
}

$andValue = $current->args[0]->value;
$andValue->setAttribute(AttributeKey::ORIGINAL_NODE, null);
$expectCall = new FuncCall(new Name('expect'), [new Arg($andValue)]);

if ($child === null) {
$segments[] = $expectCall;
} else {
$child->var = $expectCall;
$segments[] = $segmentOutermost;
}

$segmentOutermost = $current->var;
$current = $current->var;
$child = null;
}

if ($segments === []) {
return null;
}

$segments[] = $segmentOutermost;
$segments = array_reverse($segments);

$newStmts = [];
foreach ($segments as $index => $segmentExpr) {
if ($index === 0) {
$node->expr = $segmentExpr;
$newStmts[] = $node;

continue;
}

$newStmts[] = new Expression($segmentExpr);
}

return $newStmts;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

use Rector\Testing\Fixture\FixtureFileFinder;

beforeAll(function (): void {
self::$configFilePath = __DIR__.'/config/configured_rule.php';
});

test('fixtures', function (string $filePath): void {
$this->doTestFile($filePath);
})->with(
fn (): Iterator => FixtureFileFinder::yieldDirectory(__DIR__.'/Fixture')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

$a = 10;
$b = 20;

expect($a)->toBe(10)->and($b)->toBe(20)->not->toBeNull();
expect($b)->toBeInt();

?>
-----
<?php

$a = 10;
$b = 20;

expect($a)->toBe(10);
expect($b)->toBe(20)->not->toBeNull()
->toBeInt();

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

declare(strict_types=1);

use Pest\Rector\Rules\ChainExpectCallsRector;
use Pest\Rector\Rules\ConvertAndToExpectRector;
use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(ConvertAndToExpectRector::class);
$rectorConfig->ruleWithConfiguration(ChainExpectCallsRector::class, [
ChainExpectCallsRector::MERGE_DIFFERENT_VARIABLES => false,
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

use Rector\Testing\Fixture\FixtureFileFinder;

beforeAll(function (): void {
self::$configFilePath = __DIR__.'/config/configured_rule.php';
});

test('fixtures', function (string $filePath): void {
$this->doTestFile($filePath);
})->with(
fn (): Iterator => FixtureFileFinder::yieldDirectory(__DIR__.'/Fixture')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

$a = 10;
$b = 20;

expect($a)->toBe(10)->and($b)->toBe(20);

?>
-----
<?php

$a = 10;
$b = 20;

expect($a)->toBe(10);
expect($b)->toBe(20);

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

$draft = getDraft();
$riskAssessment = getRiskAssessment();

expect($draft)
->status->toBe('draft')
->approved_at->toBeNull()
->riskGroups->toHaveCount(2)
->and($riskAssessment->status)->toBe('approved');

?>
-----
<?php

$draft = getDraft();
$riskAssessment = getRiskAssessment();

expect($draft)
->status->toBe('draft')
->approved_at->toBeNull()
->riskGroups->toHaveCount(2);
expect($riskAssessment->status)->toBe('approved');

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

$a = 10;
$b = 20;
$c = null;

expect($a)->toBe(10)->toBeInt()->and($b)->toBe(20)->and($c)->not->toBeInt();

?>
-----
<?php

$a = 10;
$b = 20;
$c = null;

expect($a)->toBe(10)->toBeInt();
expect($b)->toBe(20);
expect($c)->not->toBeInt();

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

$a = 10;

expect($a)->toBe(10)->toBeInt();
Loading