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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions