rector-developer

📁 peterfox/agent-skills 📅 3 days ago
2
总安装量
2
周安装量
#73459
全站排名
安装命令
npx skills add https://github.com/peterfox/agent-skills --skill rector-developer

Agent 安装分布

opencode 2
gemini-cli 2
claude-code 2
github-copilot 2
codex 2
kimi-cli 2

Skill 文档

Rector PHP Rule Builder

Rector transforms PHP code by traversing the PHP-Parser AST, matching node types, and returning modified nodes from refactor().

Workflow

  1. Identify the PHP-Parser node type(s) to target (see references/node-types.md)
  2. Write the rule class extending AbstractRector
  3. If PHP version gated, implement MinPhpVersionInterface
  4. If configurable, implement ConfigurableRectorInterface
  5. Register the rule in rector.php config

Rule Skeleton

<?php

declare(strict_types=1);

namespace Rector\[Category]\Rector\[NodeType];

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall; // target node type
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
 * @see \Rector\Tests\[Category]\Rector\[NodeType]\[RuleName]\[RuleName]Test
 */
final class [RuleName]Rector extends AbstractRector
{
    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition('[Description]', [
            new CodeSample(
                <<<'CODE_SAMPLE'
// before
CODE_SAMPLE
                ,
                <<<'CODE_SAMPLE'
// after
CODE_SAMPLE
            ),
        ]);
    }

    /** @return array<class-string<Node>> */
    public function getNodeTypes(): array
    {
        return [FuncCall::class];
    }

    /** @param FuncCall $node */
    public function refactor(Node $node): ?Node
    {
        if (! $this->isName($node, 'target_function')) {
            return null;
        }

        // transform and return modified $node, or return null for no change
        return $node;
    }
}

refactor() Return Values

Return Effect
null No change, continue traversal
$node (modified) Replace with modified node
Node[] (non-empty) Replace with multiple nodes
NodeVisitor::REMOVE_NODE Delete the node

Never return an empty array — throws ShouldNotHappenException.

Protected Methods on AbstractRector

// Name checking
$this->isName($node, 'functionName')         // exact name match
$this->isNames($node, ['name1', 'name2'])    // match any
$this->getName($node)                         // get name string or null

// Type checking (PHPStan-powered)
$this->getType($node)                         // returns PHPStan Type
$this->isObjectType($node, new ObjectType('ClassName'))

// Traversal
$this->traverseNodesWithCallable($nodes, function (Node $node): int|Node|null {
    return null; // continue
    // or return NodeVisitor::STOP_TRAVERSAL;
    // or return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
});

// Misc
$this->mirrorComments($newNode, $oldNode);    // copy comments

Injected Services

Inject via constructor (autowired by DI container):

public function __construct(
    private readonly BetterNodeFinder $betterNodeFinder,
    // ... other services
) {}
  • $this->nodeFactory — create nodes (see references/helpers.md)
  • $this->nodeComparator — compare nodes structurally
  • $this->betterNodeFinder — search within nodes (inject via constructor)
  • PHPDoc manipulation: inject PhpDocInfoFactory + DocBlockUpdater

Configurable Rules

use Rector\Contract\Rector\ConfigurableRectorInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;

final class MyRector extends AbstractRector implements ConfigurableRectorInterface
{
    private string $targetClass = 'OldClass';

    public function configure(array $configuration): void
    {
        $this->targetClass = $configuration['target_class'] ?? $this->targetClass;
    }

    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition('...', [
            new ConfiguredCodeSample('before', 'after', ['target_class' => 'OldClass']),
        ]);
    }
}

PHP Version Gating

use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Rector\ValueObject\PhpVersionFeature;

final class MyRector extends AbstractRector implements MinPhpVersionInterface
{
    public function provideMinPhpVersion(): int
    {
        return PhpVersionFeature::ENUM; // PHP 8.1+
    }
}

See references/php-versions.md for all PhpVersionFeature constants.

rector.php Registration

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withRules([MyRector::class])
    // configurable rule:
    ->withConfiguredRule(MyConfigurableRector::class, ['key' => 'value']);

Namespace Convention

Rules live at: rules/[Category]/Rector/[NodeType]/[RuleName]Rector.php Tests live at: rules-tests/[Category]/Rector/[NodeType]/[RuleName]Rector/

Categories: CodeQuality, CodingStyle, DeadCode, EarlyReturn, Naming, Php52–Php85, Privatization, Removing, Renaming, Strict, Transform, TypeDeclaration

Writing Tests

Every rule needs a test class extending AbstractRectorTestCase and at least one fixture file.

Minimal test class (rules-tests/[Category]/Rector/[NodeType]/[RuleName]/[RuleName]RectorTest.php):

<?php

declare(strict_types=1);

namespace Rector\Tests\[Category]\Rector\[NodeType]\[RuleName];

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class [RuleName]RectorTest extends AbstractRectorTestCase
{
    #[DataProvider('provideData')]
    public function test(string $filePath): void
    {
        $this->doTestFile($filePath);
    }

    public static function provideData(): Iterator
    {
        return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
    }

    public function provideConfigFilePath(): string
    {
        return __DIR__ . '/config/configured_rule.php';
    }
}

Fixture file (Fixture/fixture.php.inc):

<?php

namespace Rector\Tests\[Category]\Rector\[NodeType]\[RuleName]\Fixture;

// INPUT code before rule runs

?>
-----
<?php

namespace Rector\Tests\[Category]\Rector\[NodeType]\[RuleName]\Fixture;

// EXPECTED code after rule runs

?>

Tip: Write only the input section, run the test, and FixtureFileUpdater auto-fills the expected output.

For a no-change case (rule should not apply), omit the ----- separator — single section only.

See references/testing.md for: config file formats, configurable rule variants, multi-config test classes, fixture naming, Source/ support classes, and fixture auto-update behaviour.

Reference Files

  • references/node-types.md — PhpParser node type quick reference (FuncCall, MethodCall, Class_, etc.)
  • references/helpers.md — NodeFactory methods, BetterNodeFinder, NodeComparator, PhpDocInfo
  • references/php-versions.md — PhpVersionFeature constants by PHP version
  • references/testing.md — Full test structure, fixture format, configurable rule testing, special cases