Skip to content

Commit e2c94bb

Browse files
committed
remove delimiters and modifiers before parsing
1 parent 842d35f commit e2c94bb

File tree

4 files changed

+41
-17
lines changed

4 files changed

+41
-17
lines changed

src/Type/Php/RegexArrayShapeMatcher.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,9 @@ private function parseGroups(string $regex): ?array
406406
return null;
407407
}
408408

409+
$rawRegex = $this->regexExpressionHelper->removeDelimitersAndModifiers($regex);
409410
try {
410-
$ast = self::$parser->parse($regex);
411+
$ast = self::$parser->parse($rawRegex);
411412
} catch (Exception) {
412413
return null;
413414
}

src/Type/Php/RegexExpressionHelper.php

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,30 +72,48 @@ public function resolve(Expr $expr): Type
7272

7373
public function getPatternModifiers(string $pattern): ?string
7474
{
75-
$delimiter = $this->getDelimiterFromString(new ConstantStringType($pattern));
76-
if ($delimiter === null) {
75+
$endDelimiterPos = $this->getEndDelimiterPos($pattern);
76+
77+
if ($endDelimiterPos === false) {
7778
return null;
7879
}
7980

81+
return substr($pattern, $endDelimiterPos + 1);
82+
}
83+
84+
public function removeDelimitersAndModifiers(string $pattern): string
85+
{
86+
$endDelimiterPos = $this->getEndDelimiterPos($pattern);
87+
88+
if ($endDelimiterPos === false) {
89+
return $pattern;
90+
}
91+
92+
return substr($pattern, 1, $endDelimiterPos - 1);
93+
}
94+
95+
private function getEndDelimiterPos(string $pattern): false|int
96+
{
97+
$startDelimiter = $this->getPatternDelimiter($pattern);
98+
if ($startDelimiter === null) {
99+
return false;
100+
}
101+
80102
// delimiter variants, see https://www.php.net/manual/en/regexp.reference.delimiters.php
81103
$bracketStyleDelimiters = [
82104
'{' => '}',
83105
'(' => ')',
84106
'[' => ']',
85107
'<' => '>',
86108
];
87-
if (array_key_exists($delimiter, $bracketStyleDelimiters)) {
88-
$endDelimiterPos = strrpos($pattern, $bracketStyleDelimiters[$delimiter]);
109+
if (array_key_exists($startDelimiter, $bracketStyleDelimiters)) {
110+
$endDelimiterPos = strrpos($pattern, $bracketStyleDelimiters[$startDelimiter]);
89111
} else {
90112
// same start and end delimiter
91-
$endDelimiterPos = strrpos($pattern, $delimiter);
113+
$endDelimiterPos = strrpos($pattern, $startDelimiter);
92114
}
93115

94-
if ($endDelimiterPos === false) {
95-
return null;
96-
}
97-
98-
return substr($pattern, $endDelimiterPos + 1);
116+
return $endDelimiterPos;
99117
}
100118

101119
/**
@@ -113,7 +131,7 @@ public function getPatternDelimiters(Concat $concat, Scope $scope): array
113131

114132
$delimiters = [];
115133
foreach ($left->getConstantStrings() as $leftString) {
116-
$delimiter = $this->getDelimiterFromString($leftString);
134+
$delimiter = $this->getPatternDelimiter($leftString->getValue());
117135
if ($delimiter === null) {
118136
continue;
119137
}
@@ -123,13 +141,13 @@ public function getPatternDelimiters(Concat $concat, Scope $scope): array
123141
return $delimiters;
124142
}
125143

126-
private function getDelimiterFromString(ConstantStringType $string): ?string
144+
private function getPatternDelimiter(string $regex): ?string
127145
{
128-
if ($string->getValue() === '') {
146+
if ($regex === '') {
129147
return null;
130148
}
131149

132-
return substr($string->getValue(), 0, 1);
150+
return substr($regex, 0, 1);
133151
}
134152

135153
}

tests/PHPStan/Analyser/nsrt/preg_match_shapes.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ function doMatch(string $s): void {
2929
assertType('array{}|array{string, non-empty-string}', $matches);
3030

3131
if (preg_match('(Price: (£|€))i', $s, $matches)) {
32-
assertType('array{string, non-empty-string, non-empty-string}', $matches);
32+
assertType('array{string, non-empty-string}', $matches);
3333
}
34-
assertType('array{}|array{string, non-empty-string, non-empty-string}', $matches);
34+
assertType('array{}|array{string, non-empty-string}', $matches);
3535

3636
if (preg_match('_foo(.)\_i_i', $s, $matches)) {
3737
assertType('array{string, non-empty-string}', $matches);

tests/PHPStan/Analyser/nsrt/preg_match_shapes_php82.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ function (string $s): void {
3939
assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches);
4040
}
4141
};
42+
function (string $s): void {
43+
if (preg_match('[(\d+)(?P<num>\d+)]n', $s, $matches)) {
44+
assertType('array{0: string, num: numeric-string, 1: numeric-string}', $matches);
45+
}
46+
};

0 commit comments

Comments
 (0)