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.
This commit is contained in:
Snider 2026-02-02 07:23:32 +00:00 committed by GitHub
parent dee598e39f
commit 5d62464627
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 237 additions and 0 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
.idea/
vendor/
claude/api/php/vendor/
__pycache__/
.env

20
Test.php Normal file
View file

@ -0,0 +1,20 @@
<?php
class Test
{
public function originalMethod()
{
// Some code before the selection
echo "Before";
// Start of selection
$a = 1;
$b = 2;
$c = $a + $b;
echo $c;
// End of selection
// Some code after the selection
echo "After";
}
}

View file

@ -0,0 +1,33 @@
---
name: refactor
description: Guided refactoring with safety checks
args: <subcommand> [args]
---
# Refactor
Guided refactoring with safety checks.
## Subcommands
- `extract-method <new-method-name>` - Extract selection to a new method
- `rename <new-name>` - Rename a class, method, or variable
- `move <new-namespace>` - Move a class to a new namespace
- `inline` - Inline a method
## Usage
```
/core:refactor extract-method validateToken
/core:refactor rename User UserV2
/core:refactor move App\\Models\\User App\\Data\\Models\\User
/core:refactor inline calculateTotal
```
## Action
This command will run the refactoring script:
```bash
~/.claude/plugins/code/scripts/refactor.php "<subcommand>" [args]
```

View file

@ -0,0 +1,108 @@
#!/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);
}

5
composer.json Normal file
View file

@ -0,0 +1,5 @@
{
"require": {
"nikic/php-parser": "^4.13"
}
}

70
composer.lock generated Normal file
View file

@ -0,0 +1,70 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8270d8d541acdfb0be968776d09dbec9",
"packages": [
{
"name": "nikic/php-parser",
"version": "v4.19.5",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/51bd93cc741b7fc3d63d20b6bdcd99fdaa359837",
"reference": "51bd93cc741b7fc3d63d20b6bdcd99fdaa359837",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=7.1"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/php-parse"
],
"type": "library",
"autoload": {
"psr-4": {
"PhpParser\\": "lib/PhpParser"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov"
}
],
"description": "A PHP parser written in PHP",
"keywords": [
"parser",
"php"
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.19.5"
},
"time": "2025-12-06T11:45:25+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.9.0"
}