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
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ parameters:
- rules/DowngradePhp80/Rector/FuncCall/DowngradeSubstrFalsyRector.php
- rules/DowngradePhp74/Rector/MethodCall/DowngradeReflectionGetTypeRector.php
- rules/DowngradePhp80/Rector/MethodCall/DowngradeReflectionGetAttributesRector.php
- rules/DowngradePhp81/Rector/MethodCall/DowngradeIsEnumRector.php
- rules/DowngradePhp73/Rector/String_/DowngradeFlexibleHeredocSyntaxRector.php
- rules/DowngradePhp80/Rector/Expression/DowngradeMatchToSwitchRector.php

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,20 @@ private function refactorExpression(Expression $expression): ?array
return null;
}

// clean the flag from the call args here; returning an array replaces the Expression,
// so the traverser never descends into the ConstFetch to clean it, and the expression
// would match again every pass and keep appending the error check
foreach ($funcCall->getArgs() as $arg) {
if (! $arg->value instanceof ConstFetch && ! $arg->value instanceof BitwiseOr) {
continue;
}

$cleanedValue = $this->jsonConstCleaner->clean($arg->value, [JsonConstant::THROW_ON_ERROR]);
if ($cleanedValue instanceof Expr) {
$arg->value = $cleanedValue;
}
}

$nodes = [$expression];
$nodes[] = new If_(
new NotIdentical(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ final class DowngradeFlexibleHeredocSyntaxRector extends AbstractRector
*/
private const array HERENOW_DOC_KINDS = [String_::KIND_HEREDOC, String_::KIND_NOWDOC];

/**
* Marker left on the doc indentation attribute once flattened, so a later pass skips the node
*/
private const string REMOVED_DOC_INDENTATION = '__REMOVED__';

public function __construct(
private readonly FollowedByNewlineOnlyMaybeWithSemicolonAnalyzer $followedByNewlineOnlyMaybeWithSemicolonAnalyzer
) {
Expand Down Expand Up @@ -80,7 +85,12 @@ public function refactor(Node $node): ?Node
return null;
}

$node->setAttribute(AttributeKey::DOC_INDENTATION, '__REMOVED__');
// already flattened on a previous pass, re-running would keep appending newlines
if ($docIndentation === self::REMOVED_DOC_INDENTATION) {
return null;
}

$node->setAttribute(AttributeKey::DOC_INDENTATION, self::REMOVED_DOC_INDENTATION);
$node->setAttribute(AttributeKey::ORIGINAL_NODE, null);

$tokens = $this->getFile()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ private function resolveMatchingReturnType(
continue;
}

// only downgrade to a wider ancestor type; skipping a narrower one keeps the rule
// idempotent, otherwise a multi-level hierarchy flips the type back and forth
if (! $parentReturnType->accepts($returnType, true)->yes()) {
continue;
}

// This is an ancestor class with a different return type
return $parentReturnType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ public function refactor(Node $node): ?FuncCall

$firstArg = $node->getArgs()[0];

// already wrapped by this rule on a previous pass; the wrapped ternary type is string|array,
// so the string check below does not catch it and the rule would keep nesting the wrap
if ($firstArg->value instanceof Ternary
&& $firstArg->value->cond instanceof FuncCall
&& $this->isName($firstArg->value->cond, 'is_array')) {
return null;
}

$commandType = $this->getType($firstArg->value);
if ($commandType->isString()->yes()) {
return null;
Expand Down
23 changes: 21 additions & 2 deletions rules/DowngradePhp81/Rector/MethodCall/DowngradeIsEnumRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Scalar\String_;
Expand All @@ -19,6 +21,8 @@
*/
final class DowngradeIsEnumRector extends AbstractRector
{
private const string IS_IF_TERNARY = 'is_if_ternary';

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Downgrades isEnum() on class reflection', [
Expand Down Expand Up @@ -52,18 +56,33 @@ public function run(ReflectionClass $reflectionClass)
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
return [Ternary::class, MethodCall::class];
}

/**
* @param MethodCall $node
* @param Ternary|MethodCall $node
*/
public function refactor(Node $node): ?Node
{
if ($node instanceof Ternary) {
if ($node->if instanceof Expr
&& $node->cond instanceof FuncCall
&& $this->isName($node->cond, 'method_exists')) {
$node->if->setAttribute(self::IS_IF_TERNARY, true);
}

return null;
}

if (! $this->isName($node->name, 'isEnum')) {
return null;
}

// already wrapped by this rule on a previous pass
if ($node->getAttribute(self::IS_IF_TERNARY) === true) {
return null;
}

if (! $this->isObjectType($node->var, new ObjectType('ReflectionClass'))) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ public function refactor(Node $node): ?Node
return null;
}

if ((string) $oldTokens[$node->getStartTokenPos()] === '(') {
// source already wraps the new in parentheses
if ((string) $oldTokens[$startTokenPos] === '(') {
return null;
}

// idempotency: on a previous pass the opening paren was prepended to the New_ var token,
// so check that token too, otherwise the rule keeps wrapping the same call every pass
if (str_starts_with((string) $oldTokens[$node->var->getStartTokenPos()], '(')) {
return null;
}

Expand Down
Loading