Fix PHPStan parser for different PHP version parsing (#1791)

This commit is contained in:
Tomas Votruba 2022-02-09 13:09:37 +01:00 committed by GitHub
parent dbadefcdf0
commit bc0eac36f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 257 additions and 108 deletions

View File

@ -0,0 +1,44 @@
# This workflow runs system tests: Use the Rector application from the source
# checkout to process "fixture" projects in e2e/ directory
# to see if those can be processed successfully
name: End to End tests on PHP 7.4
on:
pull_request:
branches:
- main
push:
branches:
- main
env:
# see https://github.com/composer/composer/issues/9368#issuecomment-718112361
COMPOSER_ROOT_VERSION: "dev-main"
jobs:
end_to_end:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
directory:
- 'e2e/php74-parse-static'
name: End to end test - ${{ matrix.directory }}
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: 7.4
coverage: none
# run in e2e subdir
-
run: composer install --ansi
working-directory: ${{ matrix.directory }}
# run e2e test - there should be no reports
- run: vendor/bin/rector --ansi --dry-run
working-directory: ${{ matrix.directory }}

View File

@ -0,0 +1,27 @@
# ref https://github.com/rectorphp/rector/issues/6970
name: Downgrade PHPStan Parser
on:
pull_request: null
env:
# see https://github.com/composer/composer/issues/9368#issuecomment-718112361
COMPOSER_ROOT_VERSION: "dev-main"
jobs:
downgrade_phpstan_parser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# see https://github.com/shivammathur/setup-php
-
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
coverage: none
- uses: "ramsey/composer-install@v1"
- run: bin/rector p vendor/clue/ndjson-react/src/Decoder.php --config tests/config/downgrade-phpstan-parser.php --dry-run

View File

@ -10,27 +10,15 @@ services:
arguments:
originalParser: @rectorParser
cachedNodesByStringCountMax: %cache.nodesByStringCountMax%
autowired: no
cachedRectorSimpleParser:
class: PHPStan\Parser\CachedParser
arguments:
originalParser: @rectorSimpleParser
cachedNodesByStringCountMax: %cache.nodesByStringCountMax%
rectorSimpleParser:
class: PHPStan\Parser\CleaningParser
arguments:
wrappedParser: @currentPhpVersionSimpleDirectParser
autowired: no
autowired: false
pathRoutingParser:
class: Rector\Core\PhpParser\Parser\RectorPathRoutingParser
class: PHPStan\Parser\PathRoutingParser
arguments:
currentPhpVersionRichParser: @cachedRectorParser
currentPhpVersionSimpleParser: @cachedRectorSimpleParser
currentPhpVersionSimpleParser: @cachedRectorParser
php8Parser: @php8Parser
autowired: no
autowired: false
rectorParser:
class: PHPStan\Parser\RichParser

View File

@ -72,6 +72,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
__DIR__ . '/../src/PhpParser/ValueObject',
__DIR__ . '/../src/functions',
__DIR__ . '/../src/constants.php',
]);
$services->alias(Application::class, ConsoleApplication::class);

View File

@ -0,0 +1,11 @@
{
"require": {
"php": "7.4.*",
"api-platform/core": "^2.6",
"symfony/framework-bundle": "^5.4",
"symfony/http-client": "^5.4"
},
"require-dev": {
"rector/rector": "dev-main"
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
use Rector\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Rector\Core\Configuration\Option;
return static function (ContainerConfigurator $containerConfigurator): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PARALLEL, true);
$parameters->set(Option::PATHS, [
__DIR__ . '/tests',
]);
$services = $containerConfigurator->services();
$services->set(DowngradeJsonDecodeNullAssociativeArgRector::class);
};

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client;
class ApiTest extends ApiTestCase
{
protected function setUp(): void
{
parent::setUp();
static::createClient();
}
}

View File

@ -603,6 +603,3 @@ parameters:
-
message: '#Array with keys is not allowed\. Use value object to pass data instead#'
path: packages/PhpAttribute/Printer/PhpAttributeGroupFactory.php
# on purpose to fix tests + old PHP version parsing code and keep compatbility
- '#Extending PHPStan\\Parser\\PathRoutingParser is not covered by backward compatibility promise\. The class might change in a minor PHPStan version#'

View File

@ -10,7 +10,6 @@ use Rector\CodingStyle\ValueObject\ReturnArrayClassMethodToYield;
use Rector\Core\Configuration\Option;
use Rector\Nette\Set\NetteSetList;
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector;
use Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector;
use Rector\PHPUnit\Set\PHPUnitSetList;
@ -74,11 +73,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
MyCLabsClassToEnumRector::class,
SpatieEnumClassToEnumRector::class,
// on purpose with private property parent - @todo fix later
ClassPropertyAssignToConstructorPromotionRector::class => [
__DIR__ . '/src/PhpParser/Parser/RectorPathRoutingParser.php',
],
// test paths
'*/tests/**/Fixture/*',
'*/rules-tests/**/Fixture/*',

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\Tests\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector\Fixture;
final class ArrayAssoc
{
public function run($data, array $items)
{
$data = \json_decode($data, $items[0]);
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector\Fixture;
final class ArrayAssoc
{
public function run($data, array $items)
{
$data = \json_decode($data, $items[0] === null ?: $items[0]);
}
}
?>

View File

@ -0,0 +1,49 @@
<?php
namespace Rector\Tests\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector\Fixture;
/**
* @see \Clue\React\NDJson\Decoder
*/
final class ClueJsonDecode
{
private $assoc;
private $depth;
private $options;
public function run($data)
{
if ($this->options === 0) {
$data = \json_decode($data, $this->assoc, $this->depth);
} else {
$data = \json_decode($data, $this->assoc, $this->depth, $this->options);
}
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector\Fixture;
/**
* @see \Clue\React\NDJson\Decoder
*/
final class ClueJsonDecode
{
private $assoc;
private $depth;
private $options;
public function run($data)
{
if ($this->options === 0) {
$data = \json_decode($data, $this->assoc === null ?: $this->assoc, $this->depth);
} else {
$data = \json_decode($data, $this->assoc === null ?: $this->assoc, $this->depth, $this->options);
}
}
}
?>

View File

@ -24,10 +24,7 @@ class PossiblyNull
{
function run(string $json, ?bool $associative)
{
if ($associative === null) {
$associative = true;
}
$value = json_decode($json, $associative);
$value = json_decode($json, $associative === null ?: $associative);
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector\Fixture;
final class SkipAlreadyDowngraded
{
public function run(string $json, $associative)
{
$value = json_decode($json, $associative === null ?: $associative);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Rector\Tests\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector\Fixture;
final class SkipClueJsonDecodeFilledInConstructor
{
private $assoc;
private $depth;
/**
* @param bool $assoc
*/
public function __construct($assoc = false)
{
$this->assoc = $assoc;
}
public function run($data)
{
$data = \json_decode($data, $this->assoc, $this->depth);
}
}

View File

@ -5,31 +5,25 @@ declare(strict_types=1);
namespace Rector\DowngradePhp72\Rector\FuncCall;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\Cast\Bool_;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Expr\Ternary;
use PHPStan\Type\BooleanType;
use Rector\Core\NodeAnalyzer\ArgsAnalyzer;
use Rector\Core\NodeManipulator\IfManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector\DowngradeJsonDecodeNullAssociativeArgRectorTest
*
* @changelog https://3v4l.org/b1mA6
*/
final class DowngradeJsonDecodeNullAssociativeArgRector extends AbstractRector
{
public function __construct(
private readonly ArgsAnalyzer $argsAnalyzer,
private readonly IfManipulator $ifManipulator
) {
}
@ -38,8 +32,6 @@ final class DowngradeJsonDecodeNullAssociativeArgRector extends AbstractRector
return new RuleDefinition('Downgrade json_decode() with null associative argument function', [
new CodeSample(
<<<'CODE_SAMPLE'
declare(strict_types=1);
function exactlyNull(string $json)
{
$value = json_decode($json, null);
@ -53,8 +45,6 @@ CODE_SAMPLE
,
<<<'CODE_SAMPLE'
declare(strict_types=1);
function exactlyNull(string $json)
{
$value = json_decode($json, true);
@ -62,10 +52,7 @@ function exactlyNull(string $json)
function possiblyNull(string $json, ?bool $associative)
{
if ($associative === null) {
$associative = true;
}
$value = json_decode($json, $associative);
$value = json_decode($json, $associative === null ?: $associative);
}
CODE_SAMPLE
),
@ -100,35 +87,25 @@ CODE_SAMPLE
$associativeValue = $args[1]->value;
if ($associativeValue instanceof Bool_) {
// already converted
if ($associativeValue instanceof Ternary && $associativeValue->if === null) {
return null;
}
$type = $this->nodeTypeResolver->getType($associativeValue);
if ($type instanceof BooleanType) {
$associativeValueType = $this->nodeTypeResolver->getType($associativeValue);
if ($associativeValueType instanceof BooleanType) {
return null;
}
if ($associativeValue instanceof ConstFetch && $this->valueResolver->isNull($associativeValue)) {
$node->args[1]->value = $this->nodeFactory->createTrue();
$args[1]->value = $this->nodeFactory->createTrue();
return $node;
}
if (! in_array(
$associativeValue::class,
[Variable::class, PropertyFetch::class, StaticPropertyFetch::class],
true
)) {
return null;
}
$currentExpression = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
$if = $this->ifManipulator->createIfExpr(
new Identical($associativeValue, $this->nodeFactory->createNull()),
new Expression(new Assign($associativeValue, $this->nodeFactory->createTrue()))
);
$this->nodesToAddCollector->addNodeBeforeNode($if, $currentExpression);
// add conditional ternary
$nullIdentical = new Identical($associativeValue, $this->nodeFactory->createNull());
$ternary = new Ternary($nullIdentical, null, $associativeValue);
$args[1]->value = $ternary;
return $node;
}

View File

@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\PhpParser\Parser;
use PhpParser\Node\Stmt;
use PHPStan\File\FileHelper;
use PHPStan\Parser\Parser;
use PHPStan\Parser\PathRoutingParser;
/**
* @api Used in PHPStan internals for parsing nodes:
* 1) with types for tests:
* 2) removing unsupported PHP-version code on real run
*/
final class RectorPathRoutingParser extends PathRoutingParser
{
private readonly Parser $currentPhpVersionRichParser;
public function __construct(
FileHelper $fileHelper,
Parser $currentPhpVersionRichParser,
Parser $currentPhpVersionSimpleParser,
Parser $php8Parser
) {
$this->currentPhpVersionRichParser = $currentPhpVersionRichParser;
parent::__construct($fileHelper, $currentPhpVersionRichParser, $currentPhpVersionSimpleParser, $php8Parser);
}
/**
* @return Stmt[]
*/
public function parseFile(string $file): array
{
// for tests, always parse nodes with directly rich parser to be aware of types
if (defined('PHPUNIT_COMPOSER_INSTALL')) {
return $this->currentPhpVersionRichParser->parseFile($file);
}
return parent::parseFile($file);
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\DowngradePhp72\Rector\FuncCall\DowngradeJsonDecodeNullAssociativeArgRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DowngradeJsonDecodeNullAssociativeArgRector::class);
};