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:
parent
dee598e39f
commit
5d62464627
6 changed files with 237 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
.idea/
|
.idea/
|
||||||
|
vendor/
|
||||||
claude/api/php/vendor/
|
claude/api/php/vendor/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
20
Test.php
Normal file
20
Test.php
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
33
claude/code/commands/refactor.md
Normal file
33
claude/code/commands/refactor.md
Normal 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]
|
||||||
|
```
|
||||||
108
claude/code/scripts/refactor.php
Normal file
108
claude/code/scripts/refactor.php
Normal 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
5
composer.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"nikic/php-parser": "^4.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
70
composer.lock
generated
Normal file
70
composer.lock
generated
Normal 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"
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue