Skip to content

Commit ab59270

Browse files
committed
PipeOperatorRule
1 parent bdc429c commit ab59270

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Operators;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\RegisteredRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Rules\RuleLevelHelper;
11+
use PHPStan\Type\ErrorType;
12+
use PHPStan\Type\Type;
13+
use function sprintf;
14+
15+
/**
16+
* @implements Rule<Node\Expr\BinaryOp\Pipe>
17+
*/
18+
#[RegisteredRule(level: 0)]
19+
final class PipeOperatorRule implements Rule
20+
{
21+
22+
public function __construct(private RuleLevelHelper $ruleLevelHelper)
23+
{
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return Node\Expr\BinaryOp\Pipe::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
$rightType = $this->ruleLevelHelper->findTypeToCheck(
34+
$scope,
35+
$node->right,
36+
'',
37+
static fn (Type $type) => $type->isCallable()->yes(),
38+
)->getType();
39+
if ($rightType instanceof ErrorType) {
40+
return [];
41+
}
42+
if (!$rightType->isCallable()->yes()) {
43+
return [];
44+
}
45+
46+
$acceptors = $rightType->getCallableParametersAcceptors($scope);
47+
foreach ($acceptors as $acceptor) {
48+
foreach ($acceptor->getParameters() as $parameter) {
49+
if ($parameter->passedByReference()->no()) {
50+
break;
51+
}
52+
53+
return [
54+
RuleErrorBuilder::message(sprintf(
55+
'Parameter #1%s of callable on the right side of pipe operator is passed by reference.',
56+
$parameter->getName() !== '' ? ' $' . $parameter->getName() : '',
57+
))->identifier('pipe.parameterPassedByReference')
58+
->build(),
59+
];
60+
}
61+
}
62+
63+
return [];
64+
}
65+
66+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Operators;
4+
5+
use PHPStan\Rules\Rule as TRule;
6+
use PHPStan\Rules\RuleLevelHelper;
7+
use PHPStan\Testing\RuleTestCase;
8+
use PHPUnit\Framework\Attributes\RequiresPhp;
9+
10+
/**
11+
* @extends RuleTestCase<PipeOperatorRule>
12+
*/
13+
class PipeOperatorRuleTest extends RuleTestCase
14+
{
15+
16+
protected function getRule(): TRule
17+
{
18+
return new PipeOperatorRule(
19+
new RuleLevelHelper(self::createReflectionProvider(), true, false, true, true, true, false, true),
20+
);
21+
}
22+
23+
#[RequiresPhp('>= 8.5')]
24+
public function testRule(): void
25+
{
26+
$this->analyse([__DIR__ . '/data/pipe-operator.php'], [
27+
[
28+
'Parameter #1 of callable on the right side of pipe operator is passed by reference.',
29+
14,
30+
],
31+
[
32+
'Parameter #1 $s of callable on the right side of pipe operator is passed by reference.',
33+
16,
34+
],
35+
]);
36+
}
37+
38+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php // lint >= 8.5
2+
3+
namespace PipeOperator;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param callable(string &): void $cb
10+
*/
11+
public function doFoo(callable $cb): void
12+
{
13+
$a = 'hello';
14+
$a |> $cb;
15+
16+
$a |> self::doBar(...);
17+
}
18+
19+
public static function doBar(string &$s): void
20+
{
21+
22+
}
23+
24+
}

0 commit comments

Comments
 (0)