Skip to content

Commit 585afd4

Browse files
authored
Add return type extension for normalize_whitespace (#298)
* Add return type extension for normalize_whitespace * Remove assertion with uppercase-string
1 parent 4a2cffe commit 585afd4

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ services:
99
class: SzepeViktor\PHPStan\WordPress\EscSqlDynamicFunctionReturnTypeExtension
1010
tags:
1111
- phpstan.broker.dynamicFunctionReturnTypeExtension
12+
-
13+
class: SzepeViktor\PHPStan\WordPress\NormalizeWhitespaceDynamicFunctionReturnTypeExtension
14+
tags:
15+
- phpstan.broker.dynamicFunctionReturnTypeExtension
1216
-
1317
class: SzepeViktor\PHPStan\WordPress\ShortcodeAttsDynamicFunctionReturnTypeExtension
1418
tags:
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SzepeViktor\PHPStan\WordPress;
6+
7+
use PhpParser\Node\Expr\FuncCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\FunctionReflection;
10+
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
11+
use PHPStan\Type\Accessory\AccessoryType;
12+
use PHPStan\Type\Constant\ConstantStringType;
13+
use PHPStan\Type\GeneralizePrecision;
14+
use PHPStan\Type\StringType;
15+
use PHPStan\Type\Type;
16+
use PHPStan\Type\TypeCombinator;
17+
use PHPStan\Type\TypeUtils;
18+
use PHPStan\Type\UnionType;
19+
20+
final class NormalizeWhitespaceDynamicFunctionReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
21+
{
22+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
23+
{
24+
return $functionReflection->getName() === 'normalize_whitespace';
25+
}
26+
27+
/**
28+
* @see https://developer.wordpress.org/reference/functions/normalize_whitespace/
29+
*
30+
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
31+
*/
32+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
33+
{
34+
if (count($functionCall->getArgs()) < 1) {
35+
return null;
36+
}
37+
38+
$argType = $scope->getType($functionCall->getArgs()[0]->value);
39+
if (! $scope->isDeclareStrictTypes()) {
40+
$argType = $argType->toString();
41+
}
42+
43+
if (! $argType->isString()->yes()) {
44+
return null;
45+
}
46+
47+
$argTypes = $argType instanceof UnionType ? $argType->getTypes() : [$argType];
48+
49+
$types = [];
50+
foreach ($argTypes as $type) {
51+
if ($type->isConstantValue()->yes()) {
52+
$types[] = $this->getTypeFromConstantString($type->getConstantStrings()[0]);
53+
continue;
54+
}
55+
56+
if ($type->isNonFalsyString()->yes()) {
57+
$types[] = $this->getTypeFromNonFalsyString($type);
58+
continue;
59+
}
60+
61+
if ($type->isNonEmptyString()->yes()) {
62+
$types[] = $this->getTypeFromNonEmptyString($type);
63+
continue;
64+
}
65+
66+
$types[] = $type;
67+
}
68+
69+
return TypeCombinator::union(...$types);
70+
}
71+
72+
private function getTypeFromConstantString(ConstantStringType $type): Type
73+
{
74+
$typeValue = $type->getValue();
75+
$type = new ConstantStringType(trim($typeValue));
76+
77+
if ($type->isNonEmptyString()->no() || $type->isNonFalsyString()->no()) {
78+
return $type;
79+
}
80+
81+
return $type->generalize(GeneralizePrecision::moreSpecific());
82+
}
83+
84+
private function getTypeFromNonFalsyString(Type $type): Type
85+
{
86+
$types = array_merge(
87+
[new StringType(), new AccessoryNonEmptyStringType()],
88+
array_filter(
89+
TypeUtils::getAccessoryTypes($type),
90+
static function (AccessoryType $accessoryType): bool {
91+
return ! $accessoryType->isNonFalsyString()->yes();
92+
}
93+
)
94+
);
95+
96+
return TypeCombinator::intersect(...$types);
97+
}
98+
99+
private function getTypeFromNonEmptyString(Type $type): Type
100+
{
101+
$types = array_merge(
102+
[new StringType()],
103+
array_filter(
104+
TypeUtils::getAccessoryTypes($type),
105+
static function (AccessoryType $accessoryType): bool {
106+
return ! $accessoryType->isNonEmptyString()->yes();
107+
}
108+
)
109+
);
110+
111+
return TypeCombinator::intersect(...$types);
112+
}
113+
}

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 self::gatherAssertTypes(__DIR__ . '/data/apply_filters.php');
1616
yield from self::gatherAssertTypes(__DIR__ . '/data/ApplyFiltersTestClass.php');
1717
yield from self::gatherAssertTypes(__DIR__ . '/data/esc_sql.php');
18+
yield from self::gatherAssertTypes(__DIR__ . '/data/normalize-whitespace.php');
1819
yield from self::gatherAssertTypes(__DIR__ . '/data/shortcode_atts.php');
1920
yield from self::gatherAssertTypes(__DIR__ . '/data/stripslashes-from-strings-only.php');
2021
yield from self::gatherAssertTypes(__DIR__ . '/data/wp_parse_url.php');
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SzepeViktor\PHPStan\WordPress\Tests;
6+
7+
use function normalize_whitespace;
8+
use function PHPStan\Testing\assertType;
9+
10+
assertType("''", normalize_whitespace(''));
11+
assertType("''", normalize_whitespace(' '));
12+
assertType("'0'", normalize_whitespace(' 0 '));
13+
14+
assertType('literal-string&lowercase-string&non-falsy-string', normalize_whitespace(' foo '));
15+
assertType('literal-string&non-falsy-string', normalize_whitespace(' Foo '));
16+
17+
/** @var non-empty-string $nonEmptyString */
18+
assertType('string', normalize_whitespace($nonEmptyString));
19+
20+
/** @var non-falsy-string $nonFalsyString */
21+
assertType('non-empty-string', normalize_whitespace($nonFalsyString));
22+
23+
/** @var lowercase-string&non-falsy-string $nonFalsyLowercaseString */
24+
assertType('lowercase-string&non-empty-string', normalize_whitespace($nonFalsyLowercaseString));
25+
26+
/** @var literal-string $literalString */
27+
assertType('literal-string', normalize_whitespace($literalString));

0 commit comments

Comments
 (0)