mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-01 08:50:50 +00:00
[PHP 8.0] Add downgrade for str_starts_with() (#126)
This commit is contained in:
parent
b42fc3dcbd
commit
f7e9986835
|
@ -56,7 +56,7 @@
|
|||
"phpstan/phpstan-nette": "^0.12.18",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"rector/rector-generator": "^0.1.7",
|
||||
"rector/phpstan-rules": "^0.2.8",
|
||||
"rector/phpstan-rules": "^0.3",
|
||||
"symplify/coding-standard": "^9.3.10",
|
||||
"symplify/easy-ci": "^9.3.10",
|
||||
"symplify/easy-coding-standard": "^9.3.10",
|
||||
|
|
|
@ -42,5 +42,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
$services->set(DowngradeClassOnObjectToGetClassRector::class);
|
||||
$services->set(DowngradeNullsafeToTernaryOperatorRector::class);
|
||||
$services->set(DowngradeTrailingCommasInParamUseRector::class);
|
||||
$services->set(\Rector\DowngradePhp80\Rector\Class_\DowngradeAttributeToAnnotationRector::class);
|
||||
$services->set(\Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector::class);
|
||||
$services->set(\Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector::class);
|
||||
};
|
||||
|
|
|
@ -47,15 +47,14 @@ final class ClassTypeResolverTest extends AbstractNodeTypeResolverTest
|
|||
|
||||
yield [__DIR__ . '/Source/ClassWithParentTrait.php', 0, new ObjectType(ClassWithParentTrait::class)];
|
||||
}
|
||||
|
||||
|
||||
public function testAnonymousClass(): void
|
||||
{
|
||||
$file = __DIR__ . '/Source/AnonymousClass.php';
|
||||
$nodePosition = 0;
|
||||
|
||||
|
||||
$variableNodes = $this->getNodesForFileOfType($file, Class_::class);
|
||||
|
||||
$resolvedType = $this->nodeTypeResolver->resolve($variableNodes[$nodePosition]);
|
||||
$resolvedType = $this->nodeTypeResolver->resolve($variableNodes[0]);
|
||||
$this->assertInstanceOf(TypeWithClassName::class, $resolvedType);
|
||||
|
||||
/** @var TypeWithClassName $resolvedType */
|
||||
|
|
|
@ -21,10 +21,9 @@ final class CommentRemover
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Node[]|Node|null $node
|
||||
* @return Node[]|null
|
||||
*/
|
||||
public function removeFromNode($node): ?array
|
||||
public function removeFromNode(array | Node | null $node): array | null
|
||||
{
|
||||
if ($node === null) {
|
||||
return null;
|
||||
|
|
|
@ -39,13 +39,12 @@ final class NodeRemover
|
|||
$this->rectorChangeCollector->notifyNodeFileInfo($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_|ClassMethod|Function_ $nodeWithStatements
|
||||
*/
|
||||
public function removeNodeFromStatements(Node $nodeWithStatements, Node $nodeToRemove): void
|
||||
{
|
||||
public function removeNodeFromStatements(
|
||||
Class_ | ClassMethod | Function_ $nodeWithStatements,
|
||||
Node $toBeRemovedNode
|
||||
): void {
|
||||
foreach ((array) $nodeWithStatements->stmts as $key => $stmt) {
|
||||
if ($nodeToRemove !== $stmt) {
|
||||
if ($toBeRemovedNode !== $stmt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace Rector\NodeTypeResolver\PHPStan\Scope;
|
|||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\Trait_;
|
||||
use PhpParser\NodeTraverser;
|
||||
|
@ -121,10 +120,7 @@ final class PHPStanNodeScopeResolver
|
|||
$nodeTraverser->traverse($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_|Interface_ $classLike
|
||||
*/
|
||||
private function resolveClassOrInterfaceScope(ClassLike $classLike, Scope $scope): Scope
|
||||
private function resolveClassOrInterfaceScope(Class_ | Interface_ $classLike, Scope $scope): Scope
|
||||
{
|
||||
$className = $this->resolveClassName($classLike);
|
||||
|
||||
|
@ -172,10 +168,7 @@ final class PHPStanNodeScopeResolver
|
|||
$this->changedFilesDetector->addFileWithDependencies($smartFileInfo, $dependentFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_|Interface_|Trait_ $classLike
|
||||
*/
|
||||
private function resolveClassName(ClassLike $classLike): string
|
||||
private function resolveClassName(Class_ | Interface_ | Trait_ $classLike): string
|
||||
{
|
||||
if (property_exists($classLike, 'namespacedName')) {
|
||||
return (string) $classLike->namespacedName;
|
||||
|
|
|
@ -4,10 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\PHPStanStaticTypeMapper;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\UnionType as PhpParserUnionType;
|
||||
use PhpParser\Node\UnionType;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\Type\Type;
|
||||
use Rector\Core\Exception\NotImplementedYetException;
|
||||
|
@ -36,11 +35,7 @@ final class PHPStanStaticTypeMapper
|
|||
throw new NotImplementedYetException(__METHOD__ . ' for ' . get_class($type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|PhpParserUnionType|null
|
||||
*/
|
||||
public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node
|
||||
{
|
||||
public function mapToPhpParserNode(Type $type, ?string $kind = null): Name | NullableType | UnionType | null {
|
||||
foreach ($this->typeMappers as $typeMapper) {
|
||||
if (! is_a($type, $typeMapper->getNodeClass(), true)) {
|
||||
continue;
|
||||
|
|
|
@ -144,10 +144,7 @@ final class UnionTypeMapper implements TypeMapperInterface
|
|||
return $unionTypeAnalysis->hasArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|null
|
||||
*/
|
||||
private function matchArrayTypes(UnionType $unionType): ?Node
|
||||
private function matchArrayTypes(UnionType $unionType): Name | NullableType | null
|
||||
{
|
||||
$unionTypeAnalysis = $this->unionTypeAnalyzer->analyseForNullableAndIterable($unionType);
|
||||
if (! $unionTypeAnalysis instanceof UnionTypeAnalysis) {
|
||||
|
|
|
@ -487,3 +487,9 @@ parameters:
|
|||
-
|
||||
message: '#Use separate function calls with readable variable names#'
|
||||
path: 'src/FileSystem/PhpFilesFinder.php'
|
||||
|
||||
# union false positive
|
||||
- '#Method Rector\\Comments\\CommentRemover\:\:removeFromNode\(\) has parameter \$node with no value type specified in iterable type array#'
|
||||
|
||||
# should be refactored to collector
|
||||
- '#Cognitive complexity for "Rector\\CodingStyle\\Naming\\NameRenamer\:\:renameNameNode\(\)" is 10, keep it under 9#'
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\CodingStyle\Rector\Use_\RemoveUnusedAliasRector\Fixture;
|
||||
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\UnionType as PhpParserUnionType;
|
||||
|
||||
final class UsedUnionType
|
||||
{
|
||||
public function param(String_ | PhpParserUnionType $node)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\CodingStyle\Rector\Use_\RemoveUnusedAliasRector\Fixture;
|
||||
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\UnionType;
|
||||
|
||||
final class UsedUnionType
|
||||
{
|
||||
public function param(String_ | UnionType $node)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -4,14 +4,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Tests\Defluent\Rector\MethodCall\InArgChainFluentMethodCallToStandaloneMethodCallRectorTest\Source;
|
||||
|
||||
use Cassandra\Date;
|
||||
use Nette\Utils\DateTime;
|
||||
|
||||
final class SetGetDateTime
|
||||
{
|
||||
/**
|
||||
* @var DateTime|null
|
||||
*/
|
||||
private $dateMin = null;
|
||||
private ?DateTime $dateMin = null;
|
||||
|
||||
public function setDateMin(?DateTime $dateTime = null)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class DowngradeStrEndsWithRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<SmartFileInfo>
|
||||
*/
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
|
||||
}
|
||||
|
||||
public function provideConfigFilePath(): string
|
||||
{
|
||||
return __DIR__ . '/config/configured_rule.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector\Fixture;
|
||||
|
||||
! str_ends_with($haystack, $needle);
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector\Fixture;
|
||||
|
||||
substr_compare($haystack, $needle, -strlen($needle)) !== 0;
|
||||
|
||||
?>
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector\Fixture;
|
||||
|
||||
str_ends_with($haystack, $needle);
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector\Fixture;
|
||||
|
||||
substr_compare($haystack, $needle, -strlen($needle)) === 0;
|
||||
|
||||
?>
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(DowngradeStrEndsWithRector::class);
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class DowngradeStrStartsWithRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator<SmartFileInfo>
|
||||
*/
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
|
||||
}
|
||||
|
||||
public function provideConfigFilePath(): string
|
||||
{
|
||||
return __DIR__ . '/config/configured_rule.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector\Fixture;
|
||||
|
||||
return ! str_starts_with($haystack, $needle);
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector\Fixture;
|
||||
|
||||
return strncmp($haystack, $needle, strlen($needle)) !== 0;
|
||||
|
||||
?>
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector\Fixture;
|
||||
|
||||
str_starts_with($haystack, $needle);
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector\Fixture;
|
||||
|
||||
strncmp($haystack, $needle, strlen($needle)) === 0;
|
||||
|
||||
?>
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(DowngradeStrStartsWithRector::class);
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Rector\Tests\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector\Fixture;
|
||||
|
||||
class NotIdentical
|
||||
final class SomeNotIdentical
|
||||
{
|
||||
public function canDrive(Car $newCar)
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ class NotIdentical
|
|||
|
||||
namespace Rector\Tests\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector\Fixture;
|
||||
|
||||
class NotIdentical
|
||||
final class SomeNotIdentical
|
||||
{
|
||||
public function canDrive(Car $newCar)
|
||||
{
|
||||
|
|
|
@ -159,11 +159,10 @@ CODE_SAMPLE
|
|||
return $this->isObjectType($classLike, $objectType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|MethodCall|StaticCall $node
|
||||
*/
|
||||
private function processPositionWithDefaultValues(Node $node, ArgumentAdder $argumentAdder): void
|
||||
{
|
||||
private function processPositionWithDefaultValues(
|
||||
ClassMethod | MethodCall | StaticCall $node,
|
||||
ArgumentAdder $argumentAdder
|
||||
): void {
|
||||
if ($this->shouldSkipParameter($node, $argumentAdder)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -30,10 +30,7 @@ final class ClassNaming
|
|||
return lcfirst($shortName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|Name|Identifier|ClassLike $name
|
||||
*/
|
||||
public function getShortName($name): string
|
||||
public function getShortName(string | Name | Identifier | ClassLike $name): string
|
||||
{
|
||||
if ($name instanceof ClassLike) {
|
||||
if ($name->name === null) {
|
||||
|
|
|
@ -14,6 +14,7 @@ use PhpParser\Node\Stmt\Class_;
|
|||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\TraitUse;
|
||||
use PhpParser\Node\UnionType;
|
||||
use Rector\CodingStyle\ValueObject\NameAndParent;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
|
||||
|
@ -60,6 +61,10 @@ final class NameRenamer
|
|||
if ($parentNode instanceof StaticCall) {
|
||||
$this->renameStaticCall($lastName, $parentNode);
|
||||
}
|
||||
|
||||
if ($parentNode instanceof UnionType) {
|
||||
$this->renameUnionType($lastName, $parentNode, $usedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,12 +110,29 @@ final class NameRenamer
|
|||
if ($param->type === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->areNamesEqual($param->type, $usedNameNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$param->type = new Name($lastName);
|
||||
}
|
||||
|
||||
private function renameUnionType(string $lastName, UnionType $unionType, Node $usedNameNode): void
|
||||
{
|
||||
foreach ($unionType->types as $key => $unionedType) {
|
||||
if (! $this->nodeNameResolver->areNamesEqual($unionedType, $usedNameNode)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $unionedType instanceof Name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unionType->types[$key] = new Name($lastName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Name|Identifier $usedNameNode
|
||||
*/
|
||||
|
|
|
@ -195,7 +195,6 @@ CODE_SAMPLE
|
|||
private function refactorAliasName(string $aliasName, string $lastName, UseUse $useUse): void
|
||||
{
|
||||
// only alias name is used → use last name directly
|
||||
|
||||
$lowerAliasName = strtolower($aliasName);
|
||||
if (! isset($this->resolvedNodeNames[$lowerAliasName])) {
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DowngradePhp80\Rector\FuncCall;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\BinaryOp\Identical;
|
||||
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
|
||||
use PhpParser\Node\Expr\BooleanNot;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\UnaryMinus;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @changelog https://wiki.php.net/rfc/add_str_starts_with_and_ends_with_functions
|
||||
*
|
||||
* @see \Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrEndsWithRector\DowngradeStrEndsWithRectorTest
|
||||
*/
|
||||
final class DowngradeStrEndsWithRector extends AbstractRector
|
||||
{
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('Downgrade str_ends_with() to strncmp() version', [
|
||||
new CodeSample(
|
||||
'str_ends_with($haystack, $needle);',
|
||||
'"" === $needle || ("" !== $haystack && 0 === substr_compare($haystack, $needle, -\strlen($needle)));'
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [FuncCall::class, BooleanNot::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FuncCall|BooleanNot $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node instanceof FuncCall && $this->isName($node->name, 'str_ends_with')) {
|
||||
return new Identical($this->createSubstrCompareFuncCall($node), new LNumber(0));
|
||||
}
|
||||
|
||||
if ($node instanceof BooleanNot) {
|
||||
$funcCall = $node->expr;
|
||||
if ($funcCall instanceof FuncCall && $this->isName($funcCall->name, 'str_ends_with')) {
|
||||
return new NotIdentical($this->createSubstrCompareFuncCall($funcCall), new LNumber(0));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function createSubstrCompareFuncCall(FuncCall $funcCall): FuncCall
|
||||
{
|
||||
$args = $funcCall->args;
|
||||
|
||||
$strlenFuncCall = $this->createStrlenFuncCall($funcCall->args[1]->value);
|
||||
$args[] = new Arg(new UnaryMinus($strlenFuncCall));
|
||||
|
||||
return new FuncCall(new Name('substr_compare'), $args);
|
||||
}
|
||||
|
||||
private function createStrlenFuncCall(Expr $expr): FuncCall
|
||||
{
|
||||
return new FuncCall(new Name('strlen'), [new Arg($expr)]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\DowngradePhp80\Rector\FuncCall;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\BinaryOp\Identical;
|
||||
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
|
||||
use PhpParser\Node\Expr\BooleanNot;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @changelog https://wiki.php.net/rfc/add_str_starts_with_and_ends_with_functions
|
||||
*
|
||||
* @see \Rector\Tests\DowngradePhp80\Rector\FuncCall\DowngradeStrStartsWithRector\DowngradeStrStartsWithRectorTest
|
||||
*/
|
||||
final class DowngradeStrStartsWithRector extends AbstractRector
|
||||
{
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('Downgrade str_starts_with() to strncmp() version', [
|
||||
new CodeSample(
|
||||
'str_starts_with($haystack, $needle);',
|
||||
'strncmp($haystack, $needle, strlen($needle)) === 0;'
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [FuncCall::class, BooleanNot::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FuncCall|BooleanNot $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node instanceof FuncCall && $this->isName($node, 'str_starts_with')) {
|
||||
return $this->createIdentical($node);
|
||||
}
|
||||
|
||||
if ($node instanceof BooleanNot) {
|
||||
$negatedCall = $node->expr;
|
||||
if ($negatedCall instanceof FuncCall && $this->isName($negatedCall, 'str_starts_with')) {
|
||||
return $this->createNotIdenticalStrncmpFuncCall($negatedCall);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function createIdentical(FuncCall $funcCall): Identical
|
||||
{
|
||||
$strlenFuncCall = $this->createStrlenFuncCall($funcCall);
|
||||
$strncmpFuncCall = $this->createStrncmpFuncCall($funcCall, $strlenFuncCall);
|
||||
|
||||
return new Identical($strncmpFuncCall, new LNumber(0));
|
||||
}
|
||||
|
||||
private function createNotIdenticalStrncmpFuncCall(FuncCall $funcCall): NotIdentical
|
||||
{
|
||||
$strlenFuncCall = $this->createStrlenFuncCall($funcCall);
|
||||
$strncmpFuncCall = $this->createStrncmpFuncCall($funcCall, $strlenFuncCall);
|
||||
|
||||
return new NotIdentical($strncmpFuncCall, new LNumber(0));
|
||||
}
|
||||
|
||||
private function createStrlenFuncCall(FuncCall $funcCall): FuncCall
|
||||
{
|
||||
return new FuncCall(new Name('strlen'), [$funcCall->args[1]]);
|
||||
}
|
||||
|
||||
private function createStrncmpFuncCall(FuncCall $funcCall, FuncCall $strlenFuncCall): FuncCall
|
||||
{
|
||||
$newArgs = $funcCall->args;
|
||||
$newArgs[] = new Arg($strlenFuncCall);
|
||||
|
||||
return new FuncCall(new Name('strncmp'), $newArgs);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Order;
|
||||
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
|
@ -26,10 +25,9 @@ final class StmtVisibilitySorter
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Class_|Trait_ $classLike
|
||||
* @return string[]
|
||||
*/
|
||||
public function sortProperties(ClassLike $classLike): array
|
||||
public function sortProperties(Class_ | Trait_ $classLike): array
|
||||
{
|
||||
$propertyRankeables = [];
|
||||
|
||||
|
@ -79,10 +77,9 @@ final class StmtVisibilitySorter
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Class_|Interface_ $classLike
|
||||
* @return string[]
|
||||
*/
|
||||
public function sortConstants(ClassLike $classLike): array
|
||||
public function sortConstants(Class_ | Interface_ $classLike): array
|
||||
{
|
||||
$classConstsRankeables = [];
|
||||
foreach ($classLike->stmts as $position => $constantStmt) {
|
||||
|
@ -103,10 +100,7 @@ final class StmtVisibilitySorter
|
|||
return $this->sortByRanksAndGetNames($classConstsRankeables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|Property|ClassConst $stmt
|
||||
*/
|
||||
private function getVisibilityLevelOrder(Stmt $stmt): int
|
||||
private function getVisibilityLevelOrder(ClassMethod | Property | ClassConst $stmt): int
|
||||
{
|
||||
if ($stmt->isPrivate()) {
|
||||
return 2;
|
||||
|
|
|
@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||
namespace Rector\Removing\NodeManipulator;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\StaticPropertyFetch;
|
||||
|
@ -80,11 +79,12 @@ final class ComplexNodeRemover
|
|||
}
|
||||
|
||||
/**
|
||||
* @param StaticPropertyFetch|PropertyFetch $expr
|
||||
* @param string[] $classMethodNamesToSkip
|
||||
*/
|
||||
private function shouldSkipPropertyForClassMethod(Expr $expr, array $classMethodNamesToSkip): bool
|
||||
{
|
||||
private function shouldSkipPropertyForClassMethod(
|
||||
StaticPropertyFetch | PropertyFetch $expr,
|
||||
array $classMethodNamesToSkip
|
||||
): bool {
|
||||
$classMethodNode = $expr->getAttribute(AttributeKey::METHOD_NODE);
|
||||
if (! $classMethodNode instanceof ClassMethod) {
|
||||
return false;
|
||||
|
@ -94,10 +94,7 @@ final class ComplexNodeRemover
|
|||
return in_array($classMethodName, $classMethodNamesToSkip, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PropertyFetch|StaticPropertyFetch $expr
|
||||
*/
|
||||
private function resolveAssign(Expr $expr): ?Assign
|
||||
private function resolveAssign(PropertyFetch | StaticPropertyFetch $expr): ?Assign
|
||||
{
|
||||
$assign = $expr->getAttribute(AttributeKey::PARENT_NODE);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use PhpParser\Node\Expr\FuncCall;
|
|||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
|
@ -131,10 +132,7 @@ CODE_SAMPLE
|
|||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|Function_|Closure $node
|
||||
*/
|
||||
private function isSkipped(Node $node): bool
|
||||
private function isSkipped(ClassMethod | Function_ | Closure $node): bool
|
||||
{
|
||||
if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) {
|
||||
return true;
|
||||
|
@ -149,7 +147,7 @@ CODE_SAMPLE
|
|||
|
||||
/**
|
||||
* @param Return_[] $returns
|
||||
* @return array<Name|NullableType|UnionType>
|
||||
* @return array<Identifier|Name|NullableType|PhpParserUnionType>
|
||||
*/
|
||||
private function collectStrictReturnTypes(array $returns): array
|
||||
{
|
||||
|
@ -212,10 +210,7 @@ CODE_SAMPLE
|
|||
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Name|NullableType|PhpParserUnionType|null
|
||||
*/
|
||||
private function resolveFuncCallReturnNode(FuncCall $funcCall): ?Node
|
||||
private function resolveFuncCallReturnNode(FuncCall $funcCall): Name | NullableType | PhpParserUnionType | null
|
||||
{
|
||||
$returnType = $this->reflectionTypeResolver->resolveFuncCallReturnType($funcCall);
|
||||
if (! $returnType instanceof Type) {
|
||||
|
@ -225,14 +220,10 @@ CODE_SAMPLE
|
|||
return $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod|Function_|Closure $functionLike
|
||||
* @param Name|NullableType|PhpParserUnionType $returnedStrictTypeNode
|
||||
*/
|
||||
private function refactorSingleReturnType(
|
||||
Return_ $return,
|
||||
Node $returnedStrictTypeNode,
|
||||
FunctionLike $functionLike
|
||||
Identifier | Name | NullableType | PhpParserUnionType $returnedStrictTypeNode,
|
||||
ClassMethod | Function_ | Closure $functionLike
|
||||
): FunctionLike {
|
||||
$resolvedType = $this->nodeTypeResolver->resolve($return);
|
||||
|
||||
|
|
|
@ -95,10 +95,7 @@ final class YieldNodesReturnTypeInferer implements ReturnTypeInfererInterface
|
|||
return $yieldNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Yield_|YieldFrom $yieldExpr
|
||||
*/
|
||||
private function resolveYieldValue(Expr $yieldExpr): ?Expr
|
||||
private function resolveYieldValue(Yield_ | YieldFrom $yieldExpr): ?Expr
|
||||
{
|
||||
if ($yieldExpr instanceof Yield_) {
|
||||
return $yieldExpr->value;
|
||||
|
|
|
@ -22,9 +22,8 @@ final class PhpVersionProvider
|
|||
|
||||
public function provide(): int
|
||||
{
|
||||
/** @var int|null $phpVersionFeatures */
|
||||
$phpVersionFeatures = $this->parameterProvider->provideParameter(Option::PHP_VERSION_FEATURES);
|
||||
if ($phpVersionFeatures !== null) {
|
||||
$phpVersionFeatures = $this->parameterProvider->provideIntParameter(Option::PHP_VERSION_FEATURES);
|
||||
if ($phpVersionFeatures > 0) {
|
||||
return $phpVersionFeatures;
|
||||
}
|
||||
|
||||
|
|
|
@ -474,12 +474,11 @@ abstract class AbstractRector extends NodeVisitorAbstract implements PhpRectorIn
|
|||
$this->nodeRemover->removeNode($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Class_|ClassMethod|Function_ $nodeWithStatements
|
||||
*/
|
||||
protected function removeNodeFromStatements(Node $nodeWithStatements, Node $nodeToRemove): void
|
||||
{
|
||||
$this->nodeRemover->removeNodeFromStatements($nodeWithStatements, $nodeToRemove);
|
||||
protected function removeNodeFromStatements(
|
||||
Class_ | ClassMethod | Function_ $nodeWithStatements,
|
||||
Node $toBeRemovedNode
|
||||
): void {
|
||||
$this->nodeRemover->removeNodeFromStatements($nodeWithStatements, $toBeRemovedNode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
'Form' => 'Collective\Html\FormFacade',
|
||||
'Html' => 'Collective\Html\HtmlFacade',
|
||||
],
|
||||
],
|
||||
]);
|
||||
]]);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user