Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Php84] Add ExplicitNullableParamTypeRector #5724

Merged
merged 11 commits into from
Mar 15, 2024
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
19 changes: 18 additions & 1 deletion build/target-repository/docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 364 Rules Overview
# 365 Rules Overview

<br>

Expand Down Expand Up @@ -46,6 +46,8 @@

- [Php83](#php83) (3)

- [Php84](#php84) (1)

- [Privatization](#privatization) (5)

- [Removing](#removing) (5)
Expand Down Expand Up @@ -5328,6 +5330,21 @@ Combine separated host and port on `ldap_connect()` args

<br>

## Php84

### ExplicitNullableParamTypeRector

Make implicit nullable param to explicit

- class: [`Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector`](../rules/Php84/Rector/Param/ExplicitNullableParamTypeRector.php)

```diff
-function foo(string $param = null) {}
+function foo(?string $param = null) {}
```

<br>

## Privatization

### FinalizeClassesWithoutChildrenRector
Expand Down
11 changes: 11 additions & 0 deletions config/set/level/up-to-php84.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->sets([SetList::PHP_84, LevelSetList::UP_TO_PHP_83]);
};
12 changes: 12 additions & 0 deletions config/set/php84.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rules([
ExplicitNullableParamTypeRector::class,
]);
};
4 changes: 2 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ parameters:
- '#Callable callable\(PHPStan\\Type\\Type\)\: PHPStan\\Type\\Type invoked with 2 parameters, 1 required#'

# known value
- '#Method Rector\\Php\\PhpVersionProvider\:\:provide\(\) should return 50200\|50300\|50400\|50500\|50600\|70000\|70100\|70200\|70300\|70400\|80000\|80100\|80200\|80300\|100000 but returns int#'
- '#Method Rector\\Php\\PhpVersionProvider\:\:provide\(\) should return 50200\|50300\|50400\|50500\|50600\|70000\|70100\|70200\|70300\|70400\|80000\|80100\|80200\|80300\|80400\|100000 but returns int#'

# stubs-rector directory exists on target-repository
-
Expand Down Expand Up @@ -279,7 +279,7 @@ parameters:
- '#Method "renamePropertyPromotion\(\)" returns bool type, so the name should start with is/has/was#'

-
message: '#Parameter \#1 \$phpVersion of method Rector\\Config\\RectorConfig\:\:phpVersion\(\) expects 50200\|50300\|50400\|50500\|50600\|70000\|70100\|70200\|70300\|70400\|80000\|80100\|80200\|80300\|100000, (.*?) given#'
message: '#Parameter \#1 \$phpVersion of method Rector\\Config\\RectorConfig\:\:phpVersion\(\) expects 50200\|50300\|50400\|50500\|50600\|70000\|70100\|70200\|70300\|70400\|80000\|80100\|80200\|80300\|80400\|100000, (.*?) given#'
path: rules-tests

-
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class ExplicitNullableParamTypeRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class WithParamTypeNullDefault
{
public function run(string $a = null)
{
}
}

?>
-----
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class WithParamTypeNullDefault
{
public function run(?string $a = null)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class SkipAlreadyNullable
{
public function run(?string $a = null)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class SkipDefaultTrue
{
public function run(bool $a = true)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class SkipNoDefault
{
public function run(bool $a)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class SkipNoType
{
public function run($a = null)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class SkipNullParam
{
public function run(null $a = null)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class UnionTypeNullDefault
{
public function run(int|string $a = null)
{
}
}

?>
-----
<?php

namespace Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\Fixture;

class UnionTypeNullDefault
{
public function run(int|string|null $a = null)
{
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector;
use Rector\ValueObject\PhpVersion;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(ExplicitNullableParamTypeRector::class);

$rectorConfig->phpVersion(PhpVersion::PHP_84);
};
87 changes: 87 additions & 0 deletions rules/Php84/Rector/Param/ExplicitNullableParamTypeRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace Rector\Php84\Rector\Param;

use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Param;
use PHPStan\Type\NullType;
use PHPStan\Type\TypeCombinator;
use Rector\PhpParser\Node\Value\ValueResolver;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\Rector\AbstractRector;
use Rector\StaticTypeMapper\StaticTypeMapper;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\Tests\Php84\Rector\Param\ExplicitNullableParamTypeRector\ExplicitNullableParamTypeRectorTest
*/
final class ExplicitNullableParamTypeRector extends AbstractRector implements MinPhpVersionInterface
{
public function __construct(
private readonly ValueResolver $valueResolver,
private readonly StaticTypeMapper $staticTypeMapper
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Make implicit nullable param to explicit', [
new CodeSample(
<<<'CODE_SAMPLE'
function foo(string $param = null) {}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
function foo(?string $param = null) {}
CODE_SAMPLE
,
),
]);
}

public function getNodeTypes(): array
{
return [Param::class];
}

/**
* @param Param $node
*/
public function refactor(Node $node): ?Param
{
if (! $node->type instanceof Node) {
return null;
}

if (! $node->default instanceof ConstFetch || ! $this->valueResolver->isNull($node->default)) {
return null;
}

$nodeType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($node->type);
if ($nodeType instanceof NullType) {
return null;
}

$removedNullNodeType = TypeCombinator::removeNull($nodeType);

if (! $nodeType->equals($removedNullNodeType)) {
return null;
}

$newNodeType = TypeCombinator::addNull($nodeType);
$node->type = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($newNodeType, TypeKind::PARAM);

return $node;
}

public function provideMinPhpVersion(): int
{
return PhpVersionFeature::DEPRECATE_IMPLICIT_NULLABLE_PARAM_TYPE;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be changed to PHP 7.1, as this is the minimum version that support the nullable types. Projects need to make this change in order to be fully compatible with PHP 7.4: #5848

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would we optional coding style preference. Since PHP 8.4 it's part of required upgrade path.

}
}
3 changes: 3 additions & 0 deletions src/Configuration/RectorConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ public function withPhpSets(
bool $php55 = false,
bool $php54 = false,
bool $php53 = false,
bool $php84 = false, // place on later as BC break when used in php 7.x without named arg
): self {
$pickedArguments = array_filter(func_get_args());
if (count($pickedArguments) > 1) {
Expand Down Expand Up @@ -429,6 +430,8 @@ public function withPhpSets(
$this->sets[] = LevelSetList::UP_TO_PHP_82;
} elseif ($php83) {
$this->sets[] = LevelSetList::UP_TO_PHP_83;
} elseif ($php84) {
$this->sets[] = LevelSetList::UP_TO_PHP_84;
}

return $this;
Expand Down
6 changes: 6 additions & 0 deletions src/Set/ValueObject/LevelSetList.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
*/
final class LevelSetList implements SetListInterface
{
/**
* @var string
*/
public const UP_TO_PHP_84 = __DIR__ . '/../../../config/set/level/up-to-php84.php';


/**
* @var string
*/
Expand Down
5 changes: 5 additions & 0 deletions src/Set/ValueObject/SetList.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ final class SetList implements SetListInterface
*/
public const PHP_83 = __DIR__ . '/../../../config/set/php83.php';

/**
* @var string
*/
public const PHP_84 = __DIR__ . '/../../../config/set/php84.php';

/**
* @var string
*/
Expand Down
5 changes: 5 additions & 0 deletions src/ValueObject/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ final class PhpVersion
*/
public const PHP_83 = 80300;

/**
* @var int
*/
public const PHP_84 = 80400;

/**
* @var int
*/
Expand Down
6 changes: 6 additions & 0 deletions src/ValueObject/PhpVersionFeature.php
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,12 @@ final class PhpVersionFeature
*/
public const TYPED_CLASS_CONSTANTS = PhpVersion::PHP_83;

/**
* @see https://wiki.php.net/rfc/deprecate-implicitly-nullable-types
* @var int
*/
public const DEPRECATE_IMPLICIT_NULLABLE_PARAM_TYPE = PhpVersion::PHP_84;

/**
* @see https://www.php.net/manual/en/migration83.deprecated.php#migration83.deprecated.ldap
* @var int
Expand Down