Skip to content

ShortenElseIfRector still breaks pure-PHP alternative syntax: emits mixed } elseif (...) : and invalid PHP #9803

Description

@tssge

Bug Report

Subject Details
Rector version 2.5.4; also reproduced with 2.4.6, 2.5.0, 2.5.2
PHP version 8.3.32 CLI
Responsible rule Rector\CodeQuality\Rector\If_\ShortenElseIfRector

ShortenElseIfRector can produce invalid PHP when rewriting a pure-PHP file that uses alternative control syntax.

This is related to #9520 / rector-src#7686, but not fixed by them: that fix only skips files where $file->containsHTML() is true. This reproducer has no inline HTML, so HTMLAverseRectorInterface does not protect it.

Rector reports success, exits 0, and writes an unparseable file.

Minimal PHP Code Causing Issue

repro.php

<?php

for ($n = 1; $n <= $t; $n++) :
    if ($n == $c) :
        $a = 1;
    else :
        if ($x || $n <= $e) :
            $a = 2;
        elseif ($d && ! $x) :
            $a = 3;
        endif;
    endif;
endfor;

rector.php

<?php

declare(strict_types=1);

use Rector\CodeQuality\Rector\If_\ShortenElseIfRector;
use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->paths([__DIR__ . '/repro.php']);
    $rectorConfig->rule(ShortenElseIfRector::class);
};

Run:

rector process repro.php
php -l repro.php

Actual result written by Rector:

<?php

for ($n = 1; $n <= $t; $n++) :
    if ($n == $c) {
        $a = 1;
    } elseif ($x || $n <= $e) {
        $a = 2;
    } elseif ($d && ! $x) :
        $a = 3;
endfor;

php -l:

PHP Parse error: syntax error, unexpected token ":" in repro.php on line 8
Errors parsing repro.php

The invalid construct is:

} elseif ($d && ! $x) :

Expected Behaviour

Either skip this transformation for alternative control syntax, or emit a syntactically consistent rewrite.

No change would be acceptable.

A full brace-style rewrite would also be acceptable:

<?php

for ($n = 1; $n <= $t; $n++) {
    if ($n == $c) {
        $a = 1;
    } elseif ($x || $n <= $e) {
        $a = 2;
    } elseif ($d && ! $x) {
        $a = 3;
    }
}

Likely cause:

rules/CodeQuality/Rector/If_/ShortenElseIfRector.php#L94-L96

$node->elseifs[] = new ElseIf_($if->cond, $if->stmts);
$node->else = $if->else;
$node->elseifs = array_merge($node->elseifs, $if->elseifs);

The newly created ElseIf_ prints as brace syntax, while the nested if's pre-existing elseif nodes can retain alternative-syntax format-preserving attributes. The result is one if/elseif chain with mixed brace/colon syntax, and the required endif; is gone.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions