Skip to content

Commit c5fe7d5

Browse files
committed
Merge branch 2.2.x into 2.3.x
2 parents cbecc88 + 239052a commit c5fe7d5

17 files changed

Lines changed: 1177 additions & 201 deletions

‎src/Rules/Functions/PrintfArrayParametersRule.php‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\Type\Constant\ConstantIntegerType;
1414
use PHPStan\Type\IntegerRangeType;
1515
use PHPStan\Type\IntegerType;
16+
use PHPStan\Type\Php\PrintfFormatParser;
1617
use PHPStan\Type\TypeCombinator;
1718
use function count;
1819
use function in_array;
@@ -28,7 +29,7 @@ final class PrintfArrayParametersRule implements Rule
2829
{
2930

3031
public function __construct(
31-
private PrintfHelper $printfHelper,
32+
private PrintfFormatParser $printfFormatParser,
3233
private ReflectionProvider $reflectionProvider,
3334
)
3435
{
@@ -66,8 +67,8 @@ public function processNode(Node $node, Scope $scope): array
6667
foreach ($formatArgType->getConstantStrings() as $formatString) {
6768
$format = $formatString->getValue();
6869

69-
$count = $this->printfHelper->getPrintfPlaceholdersCount($format);
70-
if ($count === null) {
70+
$uses = $this->printfFormatParser->parse($format);
71+
if ($uses === null) {
7172
return [
7273
RuleErrorBuilder::message(sprintf(
7374
'Call to %s contains an invalid placeholder.',
@@ -76,7 +77,7 @@ public function processNode(Node $node, Scope $scope): array
7677
];
7778
}
7879

79-
$placeHoldersCounts[] = $count;
80+
$placeHoldersCounts[] = $this->printfFormatParser->getRequiredArgumentsCount($uses);
8081
}
8182

8283
if ($placeHoldersCounts === []) {

‎src/Rules/Functions/PrintfHelper.php‎

Lines changed: 0 additions & 161 deletions
This file was deleted.

‎src/Rules/Functions/PrintfParameterTypeRule.php‎

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
use PHPStan\Type\FloatType;
1515
use PHPStan\Type\IntegerType;
1616
use PHPStan\Type\NullType;
17+
use PHPStan\Type\Php\PrintfFormatParser;
1718
use PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType;
1819
use PHPStan\Type\Type;
1920
use PHPStan\Type\UnionType;
2021
use PHPStan\Type\VerbosityLevel;
2122
use function array_key_exists;
2223
use function count;
24+
use function in_array;
2325
use function sprintf;
2426

2527
/**
@@ -40,7 +42,7 @@ final class PrintfParameterTypeRule implements Rule
4042
];
4143

4244
public function __construct(
43-
private PrintfHelper $printfHelper,
45+
private PrintfFormatParser $printfFormatParser,
4446
private ReflectionProvider $reflectionProvider,
4547
private RuleLevelHelper $ruleLevelHelper,
4648
private bool $checkStrictPrintfPlaceholderTypes,
@@ -92,7 +94,7 @@ public function processNode(Node $node, Scope $scope): array
9294

9395
$formatString = $formatArgTypeStrings[0];
9496
$format = $formatString->getValue();
95-
$placeholderMap = $this->printfHelper->getPrintfPlaceholders($format);
97+
$placeholderMap = $this->getPlaceholders($format);
9698
if ($placeholderMap === null) {
9799
// Already reported by PrintfParametersRule.
98100
return [];
@@ -171,4 +173,60 @@ public function processNode(Node $node, Scope $scope): array
171173
return $errors;
172174
}
173175

176+
/**
177+
* @return array<int, non-empty-list<PrintfPlaceholder>>|null parameter index => placeholders
178+
*/
179+
private function getPlaceholders(string $format): ?array
180+
{
181+
$uses = $this->printfFormatParser->parse($format);
182+
if ($uses === null) {
183+
return null;
184+
}
185+
186+
$placeholdersWithStar = [];
187+
foreach ($uses as $use) {
188+
if ($use['kind'] === 'value') {
189+
continue;
190+
}
191+
192+
$placeholdersWithStar[$use['number']] = true;
193+
}
194+
195+
$placeholders = [];
196+
foreach ($uses as $use) {
197+
$label = sprintf('"%s"', $use['placeholder']);
198+
if ($use['kind'] !== 'value') {
199+
$label .= sprintf(' (%s)', $use['kind']);
200+
$acceptingType = 'strict-int';
201+
} else {
202+
if (isset($placeholdersWithStar[$use['number']])) {
203+
$label .= ' (value)';
204+
}
205+
$acceptingType = $this->getAcceptingTypeBySpecifier($use['specifier']);
206+
}
207+
208+
$placeholders[$use['index']][] = new PrintfPlaceholder($label, $use['number'], $acceptingType);
209+
}
210+
211+
return $placeholders;
212+
}
213+
214+
/** @phpstan-return 'string'|'int'|'float'|'mixed' */
215+
private function getAcceptingTypeBySpecifier(string $specifier): string
216+
{
217+
if ($specifier === 's') {
218+
return 'string';
219+
}
220+
221+
if (in_array($specifier, ['d', 'u', 'c', 'o', 'x', 'X', 'b'], true)) {
222+
return 'int';
223+
}
224+
225+
if (in_array($specifier, ['e', 'E', 'f', 'F', 'g', 'G', 'h', 'H'], true)) {
226+
return 'float';
227+
}
228+
229+
return 'mixed';
230+
}
231+
174232
}

‎src/Rules/Functions/PrintfParametersRule.php‎

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
use PHPStan\Reflection\ReflectionProvider;
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\Rules\RuleErrorBuilder;
12+
use PHPStan\Type\Php\PrintfFormatParser;
13+
use ValueError;
1214
use function array_key_exists;
1315
use function count;
1416
use function in_array;
1517
use function sprintf;
18+
use function sscanf;
1619

1720
/**
1821
* @implements Rule<Node\Expr\FuncCall>
@@ -35,7 +38,7 @@ final class PrintfParametersRule implements Rule
3538
];
3639

3740
public function __construct(
38-
private PrintfHelper $printfHelper,
41+
private PrintfFormatParser $printfFormatParser,
3942
private ReflectionProvider $reflectionProvider,
4043
)
4144
{
@@ -81,9 +84,9 @@ public function processNode(Node $node, Scope $scope): array
8184
$format = $formatString->getValue();
8285

8386
if (in_array($name, ['sprintf', 'printf'], true)) {
84-
$tempPlaceHoldersCount = $this->printfHelper->getPrintfPlaceholdersCount($format);
87+
$tempPlaceHoldersCount = $this->getPrintfPlaceholdersCount($format);
8588
} else {
86-
$tempPlaceHoldersCount = $this->printfHelper->getScanfPlaceholdersCount($format);
89+
$tempPlaceHoldersCount = $this->getScanfPlaceholdersCount($format);
8790
}
8891

8992
if ($tempPlaceHoldersCount === null) {
@@ -126,4 +129,29 @@ public function processNode(Node $node, Scope $scope): array
126129
return [];
127130
}
128131

132+
private function getPrintfPlaceholdersCount(string $format): ?int
133+
{
134+
$uses = $this->printfFormatParser->parse($format);
135+
if ($uses === null) {
136+
return null;
137+
}
138+
139+
return $this->printfFormatParser->getRequiredArgumentsCount($uses);
140+
}
141+
142+
private function getScanfPlaceholdersCount(string $format): ?int
143+
{
144+
try {
145+
$result = @sscanf('', '%*n' . $format);
146+
} catch (ValueError) {
147+
return null;
148+
}
149+
150+
if ($result === null) {
151+
return null;
152+
}
153+
154+
return count($result);
155+
}
156+
129157
}

‎src/Rules/Functions/PrintfPlaceholder.php‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ final class PrintfPlaceholder
2121
/** @phpstan-param 'strict-int'|'int'|'float'|'string'|'mixed' $acceptingType */
2222
public function __construct(
2323
public readonly string $label,
24-
public readonly int $parameterIndex,
2524
public readonly int $placeholderNumber,
2625
public readonly string $acceptingType,
2726
)

0 commit comments

Comments
 (0)