rectorphp/rector · error · ShouldNotHappenException

"%s" rule is deprecated, as it can cause BC breaks. The thro

Error message

"%s" rule is deprecated, as it can cause BC breaks. The thrown JsonException has to be handled manually on every call site

What it means

Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector is deprecated: instead of removing the class, its refactor() now unconditionally throws Rector\Exception\ShouldNotHappenException with the deprecation reason. The rule used to add JSON_THROW_ON_ERROR to json_encode()/json_decode() calls, but the resulting JsonException is not caught by code written for the old null/false error style, which is a behavioral (BC) break. Because getNodeTypes() returns NodeGroup::STMTS_AWARE, the exception fires on the first analyzed file containing statements.

Source

Thrown at rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php:41

json_encode($content);
json_decode($json);
CODE_SAMPLE
, <<<'CODE_SAMPLE'
json_encode($content, JSON_THROW_ON_ERROR);
json_decode($json, null, 512, JSON_THROW_ON_ERROR);
CODE_SAMPLE
)]);
    }
    /**
     * @return array<class-string<Node>>
     */
    public function getNodeTypes(): array
    {
        return NodeGroup::STMTS_AWARE;
    }
    public function refactor(Node $node): ?Node
    {
        throw new ShouldNotHappenException(sprintf('"%s" rule is deprecated, as it can cause BC breaks. The thrown JsonException has to be handled manually on every call site', self::class));
    }
    public function provideMinPhpVersion(): int
    {
        return PhpVersionFeature::JSON_EXCEPTION;
    }
}

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Remove JsonThrowOnErrorRector from the ->withRules()/->withConfiguredRule() list in rector.php; the rule performs no migration anymore.
  2. If you want throwing JSON calls, edit each call site manually: pass JSON_THROW_ON_ERROR and wrap the call in a try/catch (JsonException) that fits the surrounding error handling.
  3. If automation is required for a controlled codebase, write a small project-scoped custom rule that adds the flag only where the call site was reviewed.
  4. As a temporary stopgap only, pin the last rector/rector version that ships the working rule; plan its removal before the next upgrade.

Example fix

// before (rector.php)
return RectorConfig::configure()
    ->withRules([JsonThrowOnErrorRector::class]);

// after (rector.php) - rule removed, migrate call sites by hand
return RectorConfig::configure()
    ->withPhp73();

// before (src/Api.php)            // after (src/Api.php)
$value = json_decode($raw);        try {
                                        $value = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
                                    } catch (JsonException $e) {
                                        throw new MalformedPayloadException($raw, $e);
                                    }
Defensive patterns

Strategy: validation

Validate before calling

use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;

$rules = [/* your withRules() list */ JsonThrowOnErrorRector::class];
$deprecated = [JsonThrowOnErrorRector::class];
$hit = array_intersect($rules, $deprecated);
if ($hit !== []) {
    throw new InvalidArgumentException('Remove deprecated rector rules before running: ' . implode(', ', $hit));
}

Try / catch

try {
    exit($rectorApplication->run());
} catch (\Rector\Exception\ShouldNotHappenException $e) {
    if (str_contains($e->getMessage(), 'rule is deprecated')) {
        // config debt, not a code problem: strip the named rule from rector.php and re-run
        fwrite(STDERR, 'Config fix needed: ' . $e->getMessage() . PHP_EOL);
        exit(1);
    }
    throw $e;
}

Prevention

When it happens

Trigger: The rule is still listed in rector.php (via ->withRules([JsonThrowOnErrorRector::class]) or ->withConfiguredRule(...)) and `vendor/bin/rector` visits any statement-aware node; refactor() is called on the first match and throws immediately.

Common situations: Upgrading rector/rector to a release where this rule was gutted while keeping an old rector.php; CI failing on the first parsed file after `composer update`; configs shared across projects that still reference the rule name.

Related errors


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/4b1708147e6e6422. Report an issue: GitHub.