Skip to content

Commit

Permalink
String::match / String::matchAll support for nette 4
Browse files Browse the repository at this point in the history
  • Loading branch information
kamil-zacek authored and ondrejmirtes committed Aug 8, 2024
1 parent 32412c9 commit ea83653
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 11 deletions.
48 changes: 43 additions & 5 deletions src/Type/Nette/StringsMatchAllDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@
namespace PHPStan\Type\Nette;

use Nette\Utils\Strings;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\Type;
use function array_key_exists;
use const PREG_OFFSET_CAPTURE;
use const PREG_PATTERN_ORDER;
use const PREG_SET_ORDER;
use const PREG_UNMATCHED_AS_NULL;

class StringsMatchAllDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
{
Expand All @@ -36,17 +44,47 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
{
$args = $methodCall->getArgs();
$patternArg = $args[1] ?? null;

if ($patternArg === null) {
return null;
}

$flagsArg = $args[2] ?? null;
$flagsType = null;
if ($flagsArg !== null) {
$flagsType = $scope->getType($flagsArg->value);
return $this->regexArrayShapeMatcher->matchAllExpr(
$patternArg->value,
$this->resolveFlagsType($args, $scope),
TrinaryLogic::createYes(),
$scope
);
}

/**
* @param array<Arg> $args
*/
private function resolveFlagsType(array $args, Scope $scope): ConstantIntegerType
{
if (!array_key_exists(2, $args)) {
return new ConstantIntegerType(PREG_SET_ORDER);
}

$captureOffsetType = $scope->getType($args[2]->value);

if ($captureOffsetType instanceof ConstantIntegerType) {
$captureOffset = $captureOffsetType->getValue();
$flags = ($captureOffset & PREG_PATTERN_ORDER) === PREG_PATTERN_ORDER ? $captureOffset : ($captureOffset | PREG_SET_ORDER);

return new ConstantIntegerType($flags);
}

return $this->regexArrayShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
$unmatchedAsNullType = array_key_exists(4, $args) ? $scope->getType($args[4]->value) : new ConstantBooleanType(false);
$patternOrderType = array_key_exists(5, $args) ? $scope->getType($args[5]->value) : new ConstantBooleanType(false);

$captureOffset = $captureOffsetType->isTrue()->yes();
$unmatchedAsNull = $unmatchedAsNullType->isTrue()->yes();
$patternOrder = $patternOrderType->isTrue()->yes();

return new ConstantIntegerType(
($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0)
);
}

}
44 changes: 38 additions & 6 deletions src/Type/Nette/StringsMatchDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
namespace PHPStan\Type\Nette;

use Nette\Utils\Strings;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function array_key_exists;
use const PREG_OFFSET_CAPTURE;
use const PREG_UNMATCHED_AS_NULL;

class StringsMatchDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
{
Expand All @@ -38,22 +44,48 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
{
$args = $methodCall->getArgs();
$patternArg = $args[1] ?? null;

if ($patternArg === null) {
return null;
}

$flagsArg = $args[2] ?? null;
$flagsType = null;
if ($flagsArg !== null) {
$flagsType = $scope->getType($flagsArg->value);
}
$arrayShape = $this->regexArrayShapeMatcher->matchExpr(
$patternArg->value,
$this->resolveFlagsType($args, $scope),
TrinaryLogic::createYes(),
$scope
);

$arrayShape = $this->regexArrayShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
if ($arrayShape === null) {
return null;
}

return TypeCombinator::union($arrayShape, new NullType());
}

/**
* @param array<Arg> $args
*/
private function resolveFlagsType(array $args, Scope $scope): ConstantIntegerType
{
if (!array_key_exists(2, $args)) {
return new ConstantIntegerType(0);
}

$captureOffsetType = $scope->getType($args[2]->value);

if ($captureOffsetType instanceof ConstantIntegerType) {
return $captureOffsetType;
}

$unmatchedAsNullType = array_key_exists(4, $args) ? $scope->getType($args[4]->value) : new ConstantBooleanType(false);

$captureOffset = $captureOffsetType->isTrue()->yes();
$unmatchedAsNull = $unmatchedAsNullType->isTrue()->yes();

return new ConstantIntegerType(
($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0)
);
}

}
10 changes: 10 additions & 0 deletions tests/Type/Nette/data/strings-match.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ function (string $s): void {
$result = Strings::matchAll($s, '/ab(?P<num>\d+)(?P<suffix>ab)?/', PREG_PATTERN_ORDER);
assertType("array{0: list<string>, num: list<numeric-string>, 1: list<numeric-string>, suffix: list<''|'ab'>, 2: list<''|'ab'>}", $result);
};

function (string $s): void {
$result = Strings::matchAll($s, '/ab(?P<num>\d+)(?P<suffix>ab)?/', false, 0, false, true);
assertType("array{0: list<string>, num: list<numeric-string>, 1: list<numeric-string>, suffix: list<''|'ab'>, 2: list<''|'ab'>}", $result);
};

function (string $s): void {
$result = Strings::matchAll($lineContent, '~\[gallery ids=(„|")(?<allIds>([0-9]+,? ?)+)(“|")~');
assertType('list<array{0: string, 1: non-empty-string, allIds: non-empty-string, 2: non-empty-string, 3: non-empty-string, 4: non-empty-string}>', $result);
};

0 comments on commit ea83653

Please sign in to comment.