Skip to content

Commit 42d1c4c

Browse files
authored
Introduce a dynamic return type extension for functions with an $echo or $display parameter (#94)
1 parent 7574a5b commit 42d1c4c

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-0
lines changed

extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ services:
6363
class: SzepeViktor\PHPStan\WordPress\ApplyFiltersDynamicFunctionReturnTypeExtension
6464
tags:
6565
- phpstan.broker.dynamicFunctionReturnTypeExtension
66+
-
67+
class: SzepeViktor\PHPStan\WordPress\EchoParameterDynamicFunctionReturnTypeExtension
68+
tags:
69+
- phpstan.broker.dynamicFunctionReturnTypeExtension
6670
-
6771
class: SzepeViktor\PHPStan\WordPress\WPErrorParameterDynamicFunctionReturnTypeExtension
6872
tags:
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/**
4+
* Set return type of various functions that support an `$echo` or `$display` parameter.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace SzepeViktor\PHPStan\WordPress;
10+
11+
use PhpParser\Node\Expr\FuncCall;
12+
use PHPStan\Analyser\Scope;
13+
use PHPStan\Reflection\FunctionReflection;
14+
use PHPStan\Type\Constant\ConstantBooleanType;
15+
use PHPStan\Type\StringType;
16+
use PHPStan\Type\Type;
17+
use PHPStan\Type\TypeCombinator;
18+
use PHPStan\Type\VoidType;
19+
20+
class EchoParameterDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
21+
{
22+
/**
23+
* Function name and position of `$echo` parameter.
24+
*/
25+
private const SUPPORTED_FUNCTIONS = [
26+
'comment_class' => 3,
27+
'edit_term_link' => 4,
28+
'get_calendar' => 1,
29+
'next_posts' => 1,
30+
'post_type_archive_title' => 1,
31+
'previous_posts' => 0,
32+
'single_cat_title' => 1,
33+
'single_post_title' => 1,
34+
'single_tag_title' => 1,
35+
'single_term_title' => 1,
36+
'the_date' => 3,
37+
'the_modified_date' => 3,
38+
'wp_loginout' => 1,
39+
'wp_register' => 2,
40+
'wp_title' => 1,
41+
];
42+
43+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
44+
{
45+
return array_key_exists($functionReflection->getName(), self::SUPPORTED_FUNCTIONS);
46+
}
47+
48+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
49+
{
50+
$name = $functionReflection->getName();
51+
$functionParameter = self::SUPPORTED_FUNCTIONS[$name] ?? null;
52+
$args = $functionCall->getArgs();
53+
54+
if ($functionParameter === null) {
55+
throw new \PHPStan\ShouldNotHappenException(
56+
sprintf(
57+
'Could not detect return types for function %s()',
58+
$name
59+
)
60+
);
61+
}
62+
63+
$echoArgumentType = new ConstantBooleanType(true);
64+
65+
if (isset($args[$functionParameter])) {
66+
$echoArgumentType = $scope->getType($args[$functionParameter]->value);
67+
}
68+
69+
if ($echoArgumentType instanceof ConstantBooleanType) {
70+
return ($echoArgumentType->getValue() === false)
71+
? new StringType()
72+
: new VoidType();
73+
}
74+
75+
return TypeCombinator::union(
76+
new StringType(),
77+
new VoidType()
78+
);
79+
}
80+
}

tests/DynamicReturnTypeExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function dataFileAsserts(): iterable
1515
yield from $this->gatherAssertTypes(__DIR__ . '/data/_get_list_table.php');
1616
yield from $this->gatherAssertTypes(__DIR__ . '/data/apply_filters.php');
1717
yield from $this->gatherAssertTypes(__DIR__ . '/data/current_time.php');
18+
yield from $this->gatherAssertTypes(__DIR__ . '/data/echo_parameter.php');
1819
yield from $this->gatherAssertTypes(__DIR__ . '/data/get_comment.php');
1920
yield from $this->gatherAssertTypes(__DIR__ . '/data/get_object_taxonomies.php');
2021
yield from $this->gatherAssertTypes(__DIR__ . '/data/get_post.php');

tests/data/echo_parameter.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SzepeViktor\PHPStan\WordPress\Tests;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
// Default value of true
10+
assertType('void', comment_class());
11+
assertType('void', edit_term_link());
12+
assertType('void', get_calendar());
13+
assertType('void', next_posts());
14+
assertType('void', post_type_archive_title());
15+
assertType('void', previous_posts());
16+
assertType('void', single_cat_title());
17+
assertType('void', single_post_title());
18+
assertType('void', single_tag_title());
19+
assertType('void', single_term_title());
20+
assertType('void', the_date());
21+
assertType('void', the_modified_date());
22+
assertType('void', wp_loginout());
23+
assertType('void', wp_register());
24+
assertType('void', wp_title());
25+
26+
// Explicit value of true
27+
$value = true;
28+
assertType('void', comment_class('', null, null, $value));
29+
assertType('void', edit_term_link('', '', '', null, $value));
30+
assertType('void', get_calendar(true, $value));
31+
assertType('void', next_posts(0, $value));
32+
assertType('void', post_type_archive_title('', $value));
33+
assertType('void', previous_posts($value));
34+
assertType('void', single_cat_title('', $value));
35+
assertType('void', single_post_title('', $value));
36+
assertType('void', single_tag_title('', $value));
37+
assertType('void', single_term_title('', $value));
38+
assertType('void', the_date('', '', '', $value));
39+
assertType('void', the_modified_date('', '', '', $value));
40+
assertType('void', wp_loginout('', $value));
41+
assertType('void', wp_register('', '', $value));
42+
assertType('void', wp_title('', $value));
43+
44+
// Explicit value of false
45+
$value = false;
46+
assertType('string', comment_class('', null, null, $value));
47+
assertType('string', edit_term_link('', '', '', null, $value));
48+
assertType('string', get_calendar(true, $value));
49+
assertType('string', next_posts(0, $value));
50+
assertType('string', post_type_archive_title('', $value));
51+
assertType('string', previous_posts(false));
52+
assertType('string', single_cat_title('', $value));
53+
assertType('string', single_post_title('', $value));
54+
assertType('string', single_tag_title('', $value));
55+
assertType('string', single_term_title('', $value));
56+
assertType('string', the_date('', '', '', $value));
57+
assertType('string', the_modified_date('', '', '', $value));
58+
assertType('string', wp_loginout('', $value));
59+
assertType('string', wp_register('', '', $value));
60+
assertType('string', wp_title('', $value));
61+
62+
// Unknown value
63+
$value = isset($_GET['foo']);
64+
assertType('string|void', comment_class('', null, null, $value));
65+
assertType('string|void', edit_term_link('', '', '', null, $value));
66+
assertType('string|void', get_calendar(true, $value));
67+
assertType('string|void', next_posts(0, $value));
68+
assertType('string|void', post_type_archive_title('', $value));
69+
assertType('string|void', previous_posts($value));
70+
assertType('string|void', single_cat_title('', $value));
71+
assertType('string|void', single_post_title('', $value));
72+
assertType('string|void', single_tag_title('', $value));
73+
assertType('string|void', single_term_title('', $value));
74+
assertType('string|void', the_date('', '', '', $value));
75+
assertType('string|void', the_modified_date('', '', '', $value));
76+
assertType('string|void', wp_loginout('', $value));
77+
assertType('string|void', wp_register('', '', $value));
78+
assertType('string|void', wp_title('', $value));

0 commit comments

Comments
 (0)