Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Implement NoReturnByReferenceRule #912

Merged
merged 1 commit into from
Feb 17, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For a full diff see [`2.6.1...main`][2.6.1...main].
### Added

- Added `Closures\NoParameterPassedByReferenceRule`, `Functions\NoParameterPassedByReferenceRule`, `Methods\NoParameterPassedByReferenceRule`, which report an error when a closure, a function, or a method has a parameter that is passed by reference ([#911]), by [@localheinz]
- Added `Functions\NoReturnByReferenceRule` and `Methods\NoReturnByReferenceRule`, which report an error when a function or a method returns by reference ([#912]), by [@localheinz]

## [`2.6.1`][2.6.1]

Expand Down Expand Up @@ -604,6 +605,7 @@ For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0].
[#897]: https://github.com/ergebnis/phpstan-rules/pull/897
[#902]: https://github.com/ergebnis/phpstan-rules/pull/902
[#911]: https://github.com/ergebnis/phpstan-rules/pull/911
[#912]: https://github.com/ergebnis/phpstan-rules/pull/912

[@cosmastech]: https://github.com/cosmastech
[@enumag]: https://github.com/enumag
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ This package provides the following rules for use with [`phpstan/phpstan`](https
- [`Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterpassedbyreferencerule)
- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnullabletypedeclarationrule)
- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnulldefaultvaluerule)
- [`Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoreturnbyreferencerule)
- [`Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule`](https://github.com/ergebnis/phpstan-rules#methodsfinalinabstractclassrule)
- [`Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoconstructorparameterwithdefaultvaluerule)
- [`Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnonullablereturntypedeclarationrule)
- [`Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterpassedbyreferencerule)
- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithcontainertypedeclarationrule)
- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnullabletypedeclarationrule)
- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnulldefaultvaluerule)
- [`Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoreturnbyreferencerule)
- [`Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule`](https://github.com/ergebnis/phpstan-rules#methodsprivateinfinalclassrule)
- [`Ergebnis\PHPStan\Rules\Statements\NoSwitchRule`](https://github.com/ergebnis/phpstan-rules#statementsnoswitchrule)

Expand Down Expand Up @@ -372,6 +374,21 @@ parameters:
enabled: false
```

#### `Functions\NoReturnByReferenceRule`

This rule reports an error when a function [returns by reference](https://www.php.net/manual/en/language.references.return.php).

##### Disabling the rule

You can set the `enabled` parameter to `false` to disable this rule.

```neon
parameters:
ergebnis:
noReturnByReference:
enabled: false
```

### Methods

#### `Methods\FinalInAbstractClassRule`
Expand Down Expand Up @@ -540,6 +557,21 @@ parameters:
enabled: false
```

#### `Functions\NoReturnByReferenceRule`

This rule reports an error when a method [returns by reference](https://www.php.net/manual/en/language.references.return.php).

##### Disabling the rule

You can set the `enabled` parameter to `false` to disable this rule.

```neon
parameters:
ergebnis:
noReturnByReference:
enabled: false
```

#### `Methods\PrivateInFinalClassRule`

This rule reports an error when a method in a `final` class is `protected` but could be `private`.
Expand Down
15 changes: 15 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ conditionalTags:
phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled%
Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule:
phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled%
Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule:
phpstan.rules.rule: %ergebnis.noReturnByReference.enabled%
Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule:
phpstan.rules.rule: %ergebnis.finalInAbstractClass.enabled%
Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule:
Expand All @@ -43,6 +45,8 @@ conditionalTags:
phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled%
Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule:
phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled%
Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule:
phpstan.rules.rule: %ergebnis.noReturnByReference.enabled%
Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule:
phpstan.rules.rule: %ergebnis.privateInFinalClass.enabled%
Ergebnis\PHPStan\Rules\Statements\NoSwitchRule:
Expand Down Expand Up @@ -84,6 +88,8 @@ parameters:
enabled: true
noParameterWithNullDefaultValue:
enabled: true
noReturnByReference:
enabled: true
noSwitch:
enabled: true
privateInFinalClass:
Expand Down Expand Up @@ -140,6 +146,9 @@ parametersSchema:
noParameterWithNullDefaultValue: structure([
enabled: bool(),
])
noReturnByReference: structure([
enabled: bool(),
])
noSwitch: structure([
enabled: bool(),
])
Expand Down Expand Up @@ -205,6 +214,9 @@ services:
-
class: Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule

-
class: Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule

-
class: Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule

Expand All @@ -229,6 +241,9 @@ services:
-
class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule

-
class: Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule

-
class: Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule

Expand Down
5 changes: 5 additions & 0 deletions src/ErrorIdentifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ public static function noNullableReturnTypeDeclaration(): self
return new self('noNullableReturnTypeDeclaration');
}

public static function noReturnByReference(): self
{
return new self('noReturnByReference');
}

public static function noSwitch(): self
{
return new self('noSwitch');
Expand Down
50 changes: 50 additions & 0 deletions src/Functions/NoReturnByReferenceRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2018-2025 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/phpstan-rules
*/

namespace Ergebnis\PHPStan\Rules\Functions;

use Ergebnis\PHPStan\Rules\ErrorIdentifier;
use PhpParser\Node;
use PHPStan\Analyser;
use PHPStan\Rules;

/**
* @implements Rules\Rule<Node\Stmt\Function_>
*/
final class NoReturnByReferenceRule implements Rules\Rule
{
public function getNodeType(): string
{
return Node\Stmt\Function_::class;
}

public function processNode(
Node $node,
Analyser\Scope $scope
): array {
if (false === $node->byRef) {
return [];
}

$message = \sprintf(
'Function %s() returns by reference.',
$node->namespacedName,
);

return [
Rules\RuleErrorBuilder::message($message)
->identifier(ErrorIdentifier::noReturnByReference()->toString())
->build(),
];
}
}
72 changes: 72 additions & 0 deletions src/Methods/NoReturnByReferenceRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2018-2025 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/phpstan-rules
*/

namespace Ergebnis\PHPStan\Rules\Methods;

use Ergebnis\PHPStan\Rules\ErrorIdentifier;
use PhpParser\Node;
use PHPStan\Analyser;
use PHPStan\Reflection;
use PHPStan\Rules;

/**
* @implements Rules\Rule<Node\Stmt\ClassMethod>
*/
final class NoReturnByReferenceRule implements Rules\Rule
{
public function getNodeType(): string
{
return Node\Stmt\ClassMethod::class;
}

public function processNode(
Node $node,
Analyser\Scope $scope
): array {
if (false === $node->byRef) {
return [];
}

$methodName = $node->name->toString();

/** @var Reflection\ClassReflection $classReflection */
$classReflection = $scope->getClassReflection();

if ($classReflection->isAnonymous()) {
$message = \sprintf(
'Method %s() in anonymous class returns by reference.',
$methodName,
);

return [
Rules\RuleErrorBuilder::message($message)
->identifier(ErrorIdentifier::noReturnByReference()->toString())
->build(),
];
}

$className = $classReflection->getName();

$message = \sprintf(
'Method %s::%s() returns by reference.',
$className,
$methodName,
);

return [
Rules\RuleErrorBuilder::message($message)
->identifier(ErrorIdentifier::noReturnByReference()->toString())
->build(),
];
}
}
14 changes: 14 additions & 0 deletions test/Fixture/Functions/NoReturnByReferenceRule/script.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Functions\NoReturnByReferenceRule;

function foo(): void
{
}

function &bar($bar)
{
return $bar;
}
13 changes: 13 additions & 0 deletions test/Fixture/Methods/NoReturnByReferenceRule/MethodInClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Methods\NoReturnByReferenceRule;

final class MethodInClass
{
public function foo($bar)
{
return $bar;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Methods\NoReturnByReferenceRule;

final class MethodInClassReturningByReference
{
public function &foo($bar)
{
return $bar;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Methods\NoReturnByReferenceRule;

interface MethodInInterface
{
public function foo($bar);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Methods\NoReturnByReferenceRule;

interface MethodInInterfaceReturningByReference
{
public function &foo($bar);
}
13 changes: 13 additions & 0 deletions test/Fixture/Methods/NoReturnByReferenceRule/MethodInTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Methods\NoReturnByReferenceRule;

trait MethodInTrait
{
public function foo($bar)
{
return $bar;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Methods\NoReturnByReferenceRule;

trait MethodInTraitReturningByReference
{
public function &foo($bar)
{
return $bar;
}
}
25 changes: 25 additions & 0 deletions test/Fixture/Methods/NoReturnByReferenceRule/script.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Ergebnis\PHPStan\Rules\Test\Fixture\Methods\NoReturnByReferenceRule;

$foo = new class() {
public function foo(): void
{
}
};

$bar = new class() {
public function foo($bar)
{
return $bar;
}
};

$baz = new class() {
public function &foo($bar)
{
return $bar;
}
};
Loading
Loading