Skip to content

Commit a8b2d76

Browse files
authored
Merge pull request #7 from ibexa/enforce-callable-return
Added rule to enforce typehints to closure and arrow functions
2 parents bd82a31 + f598a82 commit a8b2d76

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

extension.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ parameters:
77
- stubs/Money/MoneyParser.stub
88
rules:
99
- Ibexa\PHPStan\Rules\NoConfigResolverParametersInConstructorRule
10+
- Ibexa\PHPStan\Rules\RequireClosureReturnTypeRule
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\PHPStan\Rules;
10+
11+
use PhpParser\Node;
12+
use PHPStan\Analyser\Scope;
13+
use PHPStan\Rules\Rule;
14+
use PHPStan\Rules\RuleErrorBuilder;
15+
16+
/**
17+
* @implements Rule<Node\Expr>
18+
*/
19+
final class RequireClosureReturnTypeRule implements Rule
20+
{
21+
public function getNodeType(): string
22+
{
23+
return Node\Expr::class;
24+
}
25+
26+
public function processNode(Node $node, Scope $scope): array
27+
{
28+
if (!$node instanceof Node\Expr\Closure && !$node instanceof Node\Expr\ArrowFunction) {
29+
return [];
30+
}
31+
32+
if ($node->returnType === null) {
33+
$nodeType = $node instanceof Node\Expr\Closure ? 'Closure' : 'Arrow function';
34+
35+
return [
36+
RuleErrorBuilder::message(
37+
sprintf('%s is missing a return type declaration', $nodeType)
38+
)->build(),
39+
];
40+
}
41+
42+
return [];
43+
}
44+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\PHPStan\Rules\Fixtures;
10+
11+
final class RequireClosureReturnTypeFixture
12+
{
13+
public function closureWithoutReturnType(): void
14+
{
15+
// Error: Closure without return type
16+
$closure = static function ($x) {
17+
return $x * 2;
18+
};
19+
}
20+
21+
public function closureWithReturnType(): void
22+
{
23+
// OK: Closure has return type
24+
$closure = static function (int $x): int {
25+
return $x * 2;
26+
};
27+
}
28+
29+
public function arrowFunctionWithoutReturnType(): void
30+
{
31+
// Error: Arrow function without return type
32+
$arrow = static fn ($x) => $x * 2;
33+
}
34+
35+
public function arrowFunctionWithReturnType(): void
36+
{
37+
// OK: Arrow function has return type
38+
$arrow = static fn (int $x): int => $x * 2;
39+
}
40+
41+
public function closureWithVoidReturnType(): void
42+
{
43+
// OK: Closure has void return type
44+
$closure = static function (): void {
45+
echo 'Hello';
46+
};
47+
}
48+
49+
public function arrowFunctionWithMixedReturnType(): void
50+
{
51+
// OK: Arrow function has mixed return type
52+
$arrow = static fn ($x): mixed => $x;
53+
}
54+
55+
public function nestedClosuresWithoutReturnType(): void
56+
{
57+
// Error: Outer closure without return type
58+
$outer = static function () {
59+
// Error: Inner closure without return type
60+
return static function ($x) {
61+
return $x * 2;
62+
};
63+
};
64+
}
65+
66+
public function arrayMapWithoutReturnType(): void
67+
{
68+
// Error: Closure without return type
69+
$result = array_map(static function ($x) {
70+
return $x * 2;
71+
}, [1, 2, 3]);
72+
}
73+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\PHPStan\Rules;
10+
11+
use Ibexa\PHPStan\Rules\RequireClosureReturnTypeRule;
12+
use PHPStan\Rules\Rule;
13+
use PHPStan\Testing\RuleTestCase;
14+
15+
/**
16+
* @extends \PHPStan\Testing\RuleTestCase<\Ibexa\PHPStan\Rules\RequireClosureReturnTypeRule>
17+
*/
18+
final class RequireClosureReturnTypeRuleTest extends RuleTestCase
19+
{
20+
protected function getRule(): Rule
21+
{
22+
return new RequireClosureReturnTypeRule();
23+
}
24+
25+
public function testRule(): void
26+
{
27+
$this->analyse(
28+
[
29+
__DIR__ . '/Fixtures/RequireClosureReturnTypeFixture.php',
30+
],
31+
[
32+
[
33+
'Closure is missing a return type declaration',
34+
16,
35+
],
36+
[
37+
'Arrow function is missing a return type declaration',
38+
32,
39+
],
40+
[
41+
'Closure is missing a return type declaration',
42+
58,
43+
],
44+
[
45+
'Closure is missing a return type declaration',
46+
60,
47+
],
48+
[
49+
'Closure is missing a return type declaration',
50+
69,
51+
],
52+
]
53+
);
54+
}
55+
}

0 commit comments

Comments
 (0)