agent/claude/code/scripts/refactor.php
Snider 5d62464627
feat: Add initial structure for /core:refactor command (#91)
This commit introduces the initial framework for the `/core:refactor` command.

Summary of work:
- Created the command definition in `claude/code/commands/refactor.md`.
- Implemented a PHP script, `claude/code/scripts/refactor.php`, to handle the refactoring logic.
- Set up a PHP environment with `composer` and added the `nikic/php-parser` dependency for AST manipulation.
- Implemented a proof-of-concept for the `extract-method` subcommand.

Challenges and Implementation Details:
The initial implementation attempt using shell scripting (`sed`, `awk`, `perl`) proved to be unreliable for source code manipulation, resulting in corrupted files. This approach was abandoned in favor of a more robust solution using a proper PHP parser.

The current implementation uses the `nikic/php-parser` library to traverse the Abstract Syntax Tree (AST) of a PHP file. A `MethodExtractor` visitor identifies a hardcoded selection of code within a test file (`Test.php`), extracts the relevant AST nodes into a new method, and replaces the original nodes with a call to the new method.

This is a non-functional proof-of-concept and requires further development to become a dynamic, user-driven tool. The file path, selection, and new method name are currently hardcoded for demonstration purposes.
2026-02-02 07:23:32 +00:00

108 lines
3.3 KiB
PHP

#!/usr/bin/env php
<?php
require __DIR__ . '/../../../vendor/autoload.php';
use PhpParser\ParserFactory;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\PrettyPrinter;
use PhpParser\NodeVisitorAbstract;
class MethodExtractor extends NodeVisitorAbstract
{
private $startLine;
private $endLine;
private $newMethodName;
public function __construct($startLine, $endLine, $newMethodName)
{
$this->startLine = $startLine;
$this->endLine = $endLine;
$this->newMethodName = $newMethodName;
}
public function leaveNode(Node $node)
{
if ($node instanceof Class_) {
$classNode = $node;
$originalMethod = null;
$extractionStartIndex = -1;
$extractionEndIndex = -1;
foreach ($classNode->stmts as $stmt) {
if ($stmt instanceof ClassMethod) {
foreach ($stmt->stmts as $index => $mstmt) {
if ($mstmt->getStartLine() >= $this->startLine && $extractionStartIndex === -1) {
$extractionStartIndex = $index;
}
if ($mstmt->getEndLine() <= $this->endLine && $extractionStartIndex !== -1) {
$extractionEndIndex = $index;
}
}
if ($extractionStartIndex !== -1) {
$originalMethod = $stmt;
break;
}
}
}
if ($originalMethod !== null) {
$statementsToExtract = array_slice(
$originalMethod->stmts,
$extractionStartIndex,
$extractionEndIndex - $extractionStartIndex + 1
);
$newMethod = new ClassMethod($this->newMethodName, [
'stmts' => $statementsToExtract
]);
$classNode->stmts[] = $newMethod;
$methodCall = new Node\Expr\MethodCall(new Node\Expr\Variable('this'), $this->newMethodName);
$methodCallStatement = new Node\Stmt\Expression($methodCall);
array_splice(
$originalMethod->stmts,
$extractionStartIndex,
count($statementsToExtract),
[$methodCallStatement]
);
}
}
}
}
$subcommand = $argv[1] ?? null;
switch ($subcommand) {
case 'extract-method':
$filePath = 'Test.php';
$startLine = 9;
$endLine = 13;
$newMethodName = 'newMethod';
$code = file_get_contents($filePath);
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse($code);
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new MethodExtractor($startLine, $endLine, $newMethodName));
$modifiedAst = $traverser->traverse($ast);
$prettyPrinter = new PrettyPrinter\Standard;
$newCode = $prettyPrinter->prettyPrintFile($modifiedAst);
file_put_contents($filePath, $newCode);
echo "Refactoring complete.\n";
break;
default:
echo "Unknown subcommand: $subcommand\n";
exit(1);
}