Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Php/PhpVersions.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,9 @@ public function supportsDeprecatedTraits(): TrinaryLogic
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
}

public function supportsHhPrintfSpecifier(): TrinaryLogic
{
return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result;
}

}
2 changes: 1 addition & 1 deletion src/Rules/Functions/PrintfArrayParametersRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public function processNode(Node $node, Scope $scope): array
foreach ($formatArgType->getConstantStrings() as $formatString) {
$format = $formatString->getValue();

$uses = $this->printfFormatParser->parse($format);
$uses = $this->printfFormatParser->parse($format, $scope->getPhpVersion());
if ($uses === null) {
return [
RuleErrorBuilder::message(sprintf(
Expand Down
7 changes: 4 additions & 3 deletions src/Rules/Functions/PrintfParameterTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersions;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
Expand Down Expand Up @@ -94,7 +95,7 @@ public function processNode(Node $node, Scope $scope): array

$formatString = $formatArgTypeStrings[0];
$format = $formatString->getValue();
$placeholderMap = $this->getPlaceholders($format);
$placeholderMap = $this->getPlaceholders($format, $scope->getPhpVersion());
if ($placeholderMap === null) {
// Already reported by PrintfParametersRule.
return [];
Expand Down Expand Up @@ -176,9 +177,9 @@ public function processNode(Node $node, Scope $scope): array
/**
* @return array<int, non-empty-list<PrintfPlaceholder>>|null parameter index => placeholders
*/
private function getPlaceholders(string $format): ?array
private function getPlaceholders(string $format, PhpVersions $phpVersions): ?array
{
$uses = $this->printfFormatParser->parse($format);
$uses = $this->printfFormatParser->parse($format, $phpVersions);
if ($uses === null) {
return null;
}
Expand Down
7 changes: 4 additions & 3 deletions src/Rules/Functions/PrintfParametersRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Php\PhpVersions;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
Expand Down Expand Up @@ -84,7 +85,7 @@ public function processNode(Node $node, Scope $scope): array
$format = $formatString->getValue();

if (in_array($name, ['sprintf', 'printf'], true)) {
$tempPlaceHoldersCount = $this->getPrintfPlaceholdersCount($format);
$tempPlaceHoldersCount = $this->getPrintfPlaceholdersCount($format, $scope->getPhpVersion());
} else {
$tempPlaceHoldersCount = $this->getScanfPlaceholdersCount($format);
}
Expand Down Expand Up @@ -129,9 +130,9 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

private function getPrintfPlaceholdersCount(string $format): ?int
private function getPrintfPlaceholdersCount(string $format, PhpVersions $phpVersions): ?int
{
$uses = $this->printfFormatParser->parse($format);
$uses = $this->printfFormatParser->parse($format, $phpVersions);
if ($uses === null) {
return null;
}
Expand Down
14 changes: 5 additions & 9 deletions src/Type/Php/PrintfFormatParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace PHPStan\Type\Php;

use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Php\PhpVersion;
use PHPStan\Php\PhpVersions;
use function in_array;
use function ltrim;
use function strlen;
Expand All @@ -29,10 +29,6 @@ final class PrintfFormatParser

private const ARG_NUM_NEXT = -1;

public function __construct(private PhpVersion $phpVersion)
{
}

/**
* Returns the argument uses in the order the format consumes them, or null
* when the format throws ValueError for any arguments. Index 0 is the first
Expand All @@ -41,7 +37,7 @@ public function __construct(private PhpVersion $phpVersion)
*
* @return list<array{index: int, kind: 'value'|'width'|'precision', specifier: string, placeholder: string, number: int}>|null
*/
public function parse(string $format): ?array
public function parse(string $format, PhpVersions $phpVersions): ?array
{
$length = strlen($format);
$i = 0;
Expand Down Expand Up @@ -138,7 +134,7 @@ public function parse(string $format): ?array
}

$specifier = $format[$i];
if (!$this->isSpecifier($specifier)) {
if (!self::isSpecifier($specifier, $phpVersions)) {
return null;
}

Expand Down Expand Up @@ -214,13 +210,13 @@ private static function parseNumber(string $format, int $length, int &$i): int
return (int) $digits;
}

private function isSpecifier(string $char): bool
private static function isSpecifier(string $char, PhpVersions $phpVersions): bool
{
if (strpos(self::SPECIFIERS, $char) !== false) {
return true;
}

return $this->phpVersion->supportsHhPrintfSpecifier() && strpos(self::HH_SPECIFIERS, $char) !== false;
return !$phpVersions->supportsHhPrintfSpecifier()->no() && strpos(self::HH_SPECIFIERS, $char) !== false;
}

private static function isDigit(string $char): bool
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Php/PrintfFunctionThrowTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflect
$uses = [];
$requiredArgumentsCount = 0;
foreach ($formats as $format) {
$formatUses = $this->formatParser->parse($format->getValue());
$formatUses = $this->formatParser->parse($format->getValue(), $scope->getPhpVersion());
if ($formatUses === null) {
return $functionReflection->getThrowType();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

namespace PHPStan\Rules\Functions;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\Php\PrintfFormatParser;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<PrintfArrayParametersRule>
Expand All @@ -17,7 +15,7 @@ class PrintfArrayParametersRuleTest extends RuleTestCase
protected function getRule(): Rule
{
return new PrintfArrayParametersRule(
new PrintfFormatParser(new PhpVersion(PHP_VERSION_ID)),
new PrintfFormatParser(),
self::createReflectionProvider(),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

namespace PHPStan\Rules\Functions;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\Php\PrintfFormatParser;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<PrintfParameterTypeRule>
Expand All @@ -21,7 +19,7 @@ protected function getRule(): Rule
{
$reflectionProvider = $this->createReflectionProvider();
return new PrintfParameterTypeRule(
new PrintfFormatParser(new PhpVersion(PHP_VERSION_ID)),
new PrintfFormatParser(),
$reflectionProvider,
new RuleLevelHelper(
$reflectionProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\Php\PrintfFormatParser;

/**
* @extends RuleTestCase<PrintfParametersRule>
*/
class PrintfParametersRuleConfigPhpTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new PrintfParametersRule(
new PrintfFormatParser(),
self::createReflectionProvider(),
);
}

public function testHhSpecifiersInPhpVersionRange(): void
{
$this->analyse([__DIR__ . '/data/printf-format-parser-php-versions.php'], [
[
'Call to sprintf contains an invalid placeholder.',
12,
],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/data/printf-format-parser-php-version.neon',
];
}

}
3 changes: 1 addition & 2 deletions tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace PHPStan\Rules\Functions;

use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\Php\PrintfFormatParser;
Expand All @@ -17,7 +16,7 @@ class PrintfParametersRuleTest extends RuleTestCase
protected function getRule(): Rule
{
return new PrintfParametersRule(
new PrintfFormatParser(new PhpVersion(PHP_VERSION_ID)),
new PrintfFormatParser(),
self::createReflectionProvider(),
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
parameters:
phpVersion:
min: 70400
max: 80500
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace PrintfFormatParserPhpVersions;

function doFoo(float $f): void
{
sprintf('%h', $f);

if (PHP_VERSION_ID >= 80000) {
sprintf('%h', $f);
} else {
sprintf('%h', $f);
}
}
25 changes: 16 additions & 9 deletions tests/PHPStan/Type/Php/PrintfFormatParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

namespace PHPStan\Type\Php;

use PHPStan\Php\PhpVersion;
use PHPStan\Php\PhpVersions;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -79,8 +80,8 @@ public static function dataRequiredArgumentsCount(): iterable
#[DataProvider('dataRequiredArgumentsCount')]
public function testRequiredArgumentsCount(string $format, ?int $expectedCount): void
{
$parser = new PrintfFormatParser(new PhpVersion(80000));
$uses = $parser->parse($format);
$parser = new PrintfFormatParser();
$uses = $parser->parse($format, self::phpVersions(80000));
if ($expectedCount === null) {
$this->assertNull($uses);
return;
Expand All @@ -92,23 +93,29 @@ public function testRequiredArgumentsCount(string $format, ?int $expectedCount):

public function testUses(): void
{
$parser = new PrintfFormatParser(new PhpVersion(80000));
$parser = new PrintfFormatParser();
$this->assertSame([
['index' => 0, 'kind' => 'width', 'specifier' => 'g', 'placeholder' => '%*.*g', 'number' => 1],
['index' => 1, 'kind' => 'precision', 'specifier' => 'g', 'placeholder' => '%*.*g', 'number' => 1],
['index' => 2, 'kind' => 'value', 'specifier' => 'g', 'placeholder' => '%*.*g', 'number' => 1],
['index' => 0, 'kind' => 'value', 'specifier' => 's', 'placeholder' => '%1$s', 'number' => 2],
['index' => 4, 'kind' => 'width', 'specifier' => 'd', 'placeholder' => '%*5$d', 'number' => 3],
['index' => 3, 'kind' => 'value', 'specifier' => 'd', 'placeholder' => '%*5$d', 'number' => 3],
], $parser->parse('%*.*g %1$s %*5$d'));
], $parser->parse('%*.*g %1$s %*5$d', self::phpVersions(80000)));
}

public function testHhSpecifiersBeforePhp8(): void
{
$parser = new PrintfFormatParser(new PhpVersion(70400));
$this->assertNull($parser->parse('%h'));
$this->assertNull($parser->parse('%H'));
$this->assertNotNull($parser->parse('%g'));
$parser = new PrintfFormatParser();
$phpVersions = self::phpVersions(70400);
$this->assertNull($parser->parse('%h', $phpVersions));
$this->assertNull($parser->parse('%H', $phpVersions));
$this->assertNotNull($parser->parse('%g', $phpVersions));
}

private static function phpVersions(int $versionId): PhpVersions
{
return new PhpVersions(new ConstantIntegerType($versionId));
}

}
Loading