rectorphp/rector · error · ShouldNotHappenException

"%s" rule is deprecated, as too niche; use a custom rule sco

Error message

"%s" rule is deprecated, as too niche; use a custom rule scoped to your own trait and interface pair instead

What it means

Rector\Transform\Rector\Class_\AddInterfaceByTraitRector is deprecated: refactor() throws Rector\Exception\ShouldNotHappenException on the first Class_ node. The rule added a configured interface to every class that used a configured trait, but that trait-to-interface pairing is too niche to ship generically. The configure() body is an empty no-op, so old trait/interface configuration is silently ignored before the exception fires.

Source

Thrown at rules/Transform/Rector/Class_/AddInterfaceByTraitRector.php:47

{
    use SomeTrait;
}
CODE_SAMPLE
, ['SomeTrait' => 'SomeInterface'])]);
    }
    /**
     * @return array<class-string<Node>>
     */
    public function getNodeTypes(): array
    {
        return [Class_::class];
    }
    /**
     * @param Class_ $node
     */
    public function refactor(Node $node): ?Node
    {
        throw new ShouldNotHappenException(sprintf('"%s" rule is deprecated, as too niche; use a custom rule scoped to your own trait and interface pair instead', self::class));
    }
    /**
     * @param mixed[] $configuration
     */
    public function configure(array $configuration): void
    {
    }
}

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Remove AddInterfaceByTraitRector from rector.php.
  2. Add the interface to the relevant classes manually (usually a one-off, small set of classes).
  3. If the pairing is a standing convention in your codebase, write a tiny custom rector rule scoped to that exact trait/interface pair.

Example fix

// before (rector.php)
->withConfiguredRule(AddInterfaceByTraitRector::class, [
    SomeTrait::class => SomeInterface::class,
])

// after (rector.php) - removed

// before
class Service
{
    use SomeTrait;
}

// after
class Service implements SomeInterface
{
    use SomeTrait;
}
Defensive patterns

Strategy: validation

Validate before calling

use Rector\Transform\Rector\Class_\AddInterfaceByTraitRector;

$rules = [/* your list */ AddInterfaceByTraitRector::class];
if (in_array(AddInterfaceByTraitRector::class, $rules, true)) {
    throw new InvalidArgumentException('Rule deprecated; add the interface manually or via a custom scoped rule');
}

Try / catch

try {
    exit($rectorApplication->run());
} catch (\Rector\Exception\ShouldNotHappenException $e) {
    if (str_contains($e->getMessage(), 'AddInterfaceByTraitRector')) {
        fwrite(STDERR, 'Remove the rule; the old trait=>interface configuration is ignored.' . PHP_EOL);
        exit(1);
    }
    throw $e;
}

Prevention

When it happens

Trigger: The rule is still referenced in rector.php (withRules() or withConfiguredRule() with the old trait=>interface map) and rector visits any class; refactor() throws immediately on the first Class_ node.

Common situations: Legacy configs from projects that bound e.g. a Laravel trait to a custom interface; rector upgrade breaking CI at the first class; configurations where the mapping was already ignored for a while without anyone noticing.

Related errors


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