Fix code quality violations and add VS Code extension features

Fix VS Code extension storage paths for new directory structure
Fix jqhtml compiled files missing from bundle
Fix bundle babel transformation and add rsxrealpath() function

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-22 00:43:05 +00:00
parent 53d359bc91
commit 37a6183eb4
80 changed files with 1066 additions and 255 deletions

View File

@@ -18,7 +18,9 @@ error_reporting(E_ALL);
ini_set('display_errors', 0);
// Base path - framework is in system/ subdirectory
// @REALPATH-EXCEPTION - Bootstrap file: runs before helpers loaded, needs PHP's realpath()
$system_path = realpath(__DIR__ . '/../../../..');
// @REALPATH-EXCEPTION - Bootstrap file: runs before helpers loaded, needs PHP's realpath()
define('IDE_BASE_PATH', realpath($system_path . '/..')); // Project root
define('IDE_SYSTEM_PATH', $system_path); // Framework root
@@ -27,6 +29,13 @@ function ide_framework_path($relative_path) {
return IDE_SYSTEM_PATH . '/' . ltrim($relative_path, '/');
}
// Define Laravel helper that exec_safe() needs
// This standalone handler never runs with Laravel, so no conflict
function storage_path($path = '') {
$base = IDE_SYSTEM_PATH . '/storage';
return $path ? $base . '/' . ltrim($path, '/') : $base;
}
// Load exec_safe() function
require_once ide_framework_path('app/RSpade/helpers.php');
@@ -74,6 +83,11 @@ $service_path = trim($service_path, '/');
$request_body = file_get_contents('php://input');
$request_data = json_decode($request_body, true);
// Merge GET parameters (for backward compatibility with /_idehelper endpoints)
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$request_data = array_merge($request_data ?? [], $_GET);
}
// Handle authentication
$auth_data = [];
$session_header = $_SERVER['HTTP_X_SESSION'] ?? null;
@@ -176,6 +190,18 @@ switch ($service_path) {
json_response(['success' => true, 'service' => 'ide', 'version' => '1.0.0']);
break;
case 'resolve_class':
handle_resolve_class_service($request_data);
break;
case 'js_lineage':
handle_js_lineage_service($request_data);
break;
case 'resolve_url':
handle_resolve_url_service($request_data);
break;
default:
error_response('Unknown service: ' . $service_path, 404);
}
@@ -203,6 +229,7 @@ function handle_format_service($data) {
// Get real directory path
if (is_dir($dir)) {
// @REALPATH-EXCEPTION - IDE service: runs standalone without framework helpers
$real_dir = realpath($dir);
if ($real_dir) {
$full_path = $real_dir . '/' . $filename;
@@ -633,6 +660,7 @@ function handle_git_diff_service($data) {
$filename = basename($full_path);
if (is_dir($dir)) {
// @REALPATH-EXCEPTION - IDE service: runs standalone without framework helpers
$real_dir = realpath($dir);
if ($real_dir) {
$full_path = $real_dir . '/' . $filename;
@@ -754,4 +782,472 @@ function handle_command_service($data) {
'output' => $combined_output,
'exit_code' => $return_var
]);
}
/**
* Handle resolve_class service - resolve class/view/component definitions
* This is a port of the Ide_Helper_Controller::resolve_class() method
*/
function handle_resolve_class_service($data) {
$identifier = $data['class'] ?? $data['identifier'] ?? null;
$method_name = $data['method'] ?? null;
$type = $data['type'] ?? null;
if (!$identifier) {
json_response([
'error' => 'Missing required parameter: class or identifier',
'found' => false,
], 400);
}
// Load manifest data
$manifest_file = ide_framework_path('storage/rsx-build/manifest_data.php');
if (!file_exists($manifest_file)) {
error_response('Manifest not found - run php artisan rsx:manifest:build', 500);
}
$manifest_raw = include $manifest_file;
$manifest_data = $manifest_raw['data'] ?? $manifest_raw;
$files = $manifest_data['files'] ?? [];
// Helper function to find PHP class in manifest
$find_php_class = function($class_name) use ($files) {
foreach ($files as $file_path => $file_data) {
if (isset($file_data['class']) && $file_data['class'] === $class_name) {
return $file_data;
}
}
return null;
};
// Helper function to find view in manifest
$find_view = function($view_name) use ($files) {
foreach ($files as $file_path => $file_data) {
if (isset($file_data['id']) && $file_data['id'] === $view_name) {
return $file_data;
}
}
return null;
};
// Helper function to convert PascalCase to snake_case
$camel_to_snake = function($input) {
$result = preg_replace('/(?<!^)[A-Z]/', '_$0', $input);
return strtolower($result);
};
// Helper function to convert snake_case to PascalCase
$snake_to_pascal = function($input) {
$parts = explode('_', $input);
return implode('_', array_map('ucfirst', $parts));
};
// Priority 1: Try as PHP class
if (!$type || $type === 'class' || preg_match('/^[A-Z][A-Za-z0-9_]*$/', $identifier)) {
$class_data = $find_php_class($identifier);
if ($class_data) {
$file_path = $class_data['file'];
$line_number = 1;
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
$lines = explode("\n", $content);
if ($method_name) {
// Check if method metadata exists
if (isset($class_data['public_static_methods'][$method_name])) {
$method_metadata = $class_data['public_static_methods'][$method_name];
if (isset($method_metadata['line'])) {
$line_number = $method_metadata['line'];
}
} else if (isset($class_data['public_instance_methods'][$method_name])) {
$method_metadata = $class_data['public_instance_methods'][$method_name];
if (isset($method_metadata['line'])) {
$line_number = $method_metadata['line'];
}
}
// If we don't have line number from metadata, search manually
if ($line_number === 1 && !empty($lines)) {
$in_class_or_trait = false;
$brace_count = 0;
foreach ($lines as $index => $line) {
if (preg_match('/^\s*(class|trait)\s+\w+(\s|$)/', $line)) {
$in_class_or_trait = true;
}
if ($in_class_or_trait) {
$brace_count += substr_count($line, '{');
$brace_count -= substr_count($line, '}');
if (preg_match('/^\s*(public|protected|private)?\s*(static\s+)?function\s+' . preg_quote($method_name, '/') . '\s*\(/', $line)) {
$line_number = $index + 1;
break;
}
if ($brace_count === 0 && strpos($line, '}') !== false) {
break;
}
}
}
}
} else {
// Find the line with the class definition
foreach ($lines as $index => $line) {
if (preg_match('/^\s*class\s+' . preg_quote($identifier, '/') . '(\s|$)/', $line)) {
$line_number = $index + 1;
break;
}
}
}
}
$response_data = [
'found' => true,
'type' => 'class',
'file' => $file_path,
'line' => $line_number,
'metadata' => [
'namespace' => $class_data['namespace'] ?? null,
'extends' => $class_data['extends'] ?? null,
'fqcn' => $class_data['fqcn'] ?? null,
],
];
if ($method_name) {
$response_data['method'] = $method_name;
}
json_response($response_data);
}
}
// Priority 2: Try as RSX blade view
if (!$type || $type === 'view' || !preg_match('/Controller$/', $identifier)) {
$view_data = $find_view($identifier);
if ($view_data) {
$file_path = $view_data['file'];
$line_number = 1;
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
$lines = explode("\n", $content);
// Look for @rsx_id('identifier')
foreach ($lines as $index => $line) {
if (preg_match('/@rsx_id\s*\(\s*[\'"]' . preg_quote($identifier, '/') . '[\'"]\s*\)/', $line)) {
$line_number = $index + 1;
break;
}
}
}
json_response([
'found' => true,
'type' => 'view',
'file' => $file_path,
'line' => $line_number,
'identifier' => $identifier,
]);
}
}
// Priority 3: Try as bundle alias
if (!$type || $type === 'bundle_alias' || preg_match('/^[a-z0-9]+$/', $identifier)) {
$config_path = IDE_SYSTEM_PATH . '/config/rsx.php';
if (file_exists($config_path)) {
$config = include $config_path;
if (isset($config['bundle_aliases'][$identifier])) {
$bundle_class = $config['bundle_aliases'][$identifier];
$class_parts = explode('\\', $bundle_class);
$class_name = end($class_parts);
$class_data = $find_php_class($class_name);
if ($class_data) {
$file_path = $class_data['file'];
$line_number = 1;
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
$lines = explode("\n", $content);
foreach ($lines as $index => $line) {
if (preg_match('/^\s*class\s+' . preg_quote($class_name, '/') . '(\s|$)/', $line)) {
$line_number = $index + 1;
break;
}
}
}
json_response([
'found' => true,
'type' => 'bundle_alias',
'file' => $file_path,
'line' => $line_number,
'identifier' => $identifier,
'resolved_class' => $bundle_class,
]);
}
}
}
}
// Priority 4: jqhtml template files
if ($type === 'jqhtml_template') {
$component_snake = $camel_to_snake($identifier);
foreach ($files as $file_path => $file_data) {
if (str_ends_with($file_path, '.jqhtml')) {
$basename = basename($file_path, '.jqhtml');
if ($basename === $component_snake || $snake_to_pascal($basename) === $identifier) {
$line_number = 1;
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
$lines = explode("\n", $content);
foreach ($lines as $index => $line) {
if (preg_match('/<Define:\s*' . preg_quote($identifier, '/') . '/', $line)) {
$line_number = $index + 1;
break;
}
}
}
json_response([
'found' => true,
'type' => 'jqhtml_template',
'file' => $file_path,
'line' => $line_number,
'identifier' => $identifier,
]);
}
}
}
}
// Priority 5: jqhtml JavaScript classes
if ($type === 'jqhtml_class') {
foreach ($files as $file_path => $file_data) {
if (str_ends_with($file_path, '.js')) {
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends\s+[A-Za-z_]*Jqhtml_Component/', $content)) {
$lines = explode("\n", $content);
$line_number = 1;
foreach ($lines as $index => $line) {
if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends/', $line)) {
$line_number = $index + 1;
break;
}
}
json_response([
'found' => true,
'type' => 'jqhtml_class',
'file' => $file_path,
'line' => $line_number,
'identifier' => $identifier,
]);
}
}
}
}
}
// Priority 6: jqhtml class methods
if ($type === 'jqhtml_class_method' && $method_name) {
$search_method = ($method_name === 'data') ? 'on_load' : $method_name;
foreach ($files as $file_path => $file_data) {
if (str_ends_with($file_path, '.js')) {
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends\s+[A-Za-z_]*Jqhtml_Component/', $content)) {
$lines = explode("\n", $content);
$line_number = 1;
$in_class = false;
$brace_count = 0;
foreach ($lines as $index => $line) {
if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends/', $line)) {
$in_class = true;
}
if ($in_class) {
$brace_count += substr_count($line, '{');
$brace_count -= substr_count($line, '}');
if (preg_match('/(?:async\s+)?' . preg_quote($search_method, '/') . '\s*\(/', $line)) {
$line_number = $index + 1;
break;
}
if ($brace_count === 0 && strpos($line, '}') !== false) {
break;
}
}
}
json_response([
'found' => true,
'type' => 'jqhtml_class_method',
'file' => $file_path,
'line' => $line_number,
'identifier' => $identifier,
'method' => $method_name,
]);
}
}
}
}
}
// Nothing found
json_response([
'found' => false,
'error' => 'Identifier not found in manifest',
'identifier' => $identifier,
'searched_types' => ['class', 'view', 'bundle_alias', 'jqhtml_template', 'jqhtml_class', 'jqhtml_class_method'],
]);
}
/**
* Handle js_lineage service - get JavaScript class inheritance chain
* This is a port of the Ide_Helper_Controller::js_lineage() method
*/
function handle_js_lineage_service($data) {
$class_name = $data['class'] ?? null;
if (!$class_name) {
json_response([
'error' => 'Missing required parameter: class',
], 400);
}
// Load manifest data
$manifest_file = ide_framework_path('storage/rsx-build/manifest_data.php');
if (!file_exists($manifest_file)) {
error_response('Manifest not found - run php artisan rsx:manifest:build', 500);
}
$manifest_raw = include $manifest_file;
$manifest_data = $manifest_raw['data'] ?? $manifest_raw;
$files = $manifest_data['files'] ?? [];
// Find the JavaScript class and trace its lineage
$lineage = [];
$current_class = $class_name;
// Helper to find extends clause in JS file
$find_extends = function($file_path) {
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
if (preg_match('/class\s+\w+\s+extends\s+([A-Za-z_][A-Za-z0-9_]*)/', $content, $matches)) {
return $matches[1];
}
}
return null;
};
// Trace up to 10 levels to prevent infinite loops
for ($i = 0; $i < 10; $i++) {
$found = false;
foreach ($files as $file_path => $file_data) {
if (str_ends_with($file_path, '.js')) {
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
if (file_exists($absolute_path)) {
$content = file_get_contents($absolute_path);
// Check if this file defines the current class
if (preg_match('/class\s+' . preg_quote($current_class, '/') . '\s+extends\s+([A-Za-z_][A-Za-z0-9_]*)/', $content, $matches)) {
$parent_class = $matches[1];
$lineage[] = $parent_class;
$current_class = $parent_class;
$found = true;
break;
}
}
}
}
if (!$found) {
break;
}
}
json_response([
'class' => $class_name,
'lineage' => $lineage,
]);
}
/**
* Handle resolve_url service - resolve URL to controller/method
* Takes a URL path and returns the controller and method that handles it
*/
function handle_resolve_url_service($data) {
$url = $data['url'] ?? null;
if (!$url) {
json_response([
'error' => 'Missing required parameter: url',
'found' => false,
], 400);
}
// Load manifest to get routes
$manifest_file = ide_framework_path('storage/rsx-build/manifest_data.php');
if (!file_exists($manifest_file)) {
error_response('Manifest not found - run php artisan rsx:manifest:build', 500);
}
$manifest_raw = include $manifest_file;
$manifest_data = $manifest_raw['data'] ?? $manifest_raw;
// Get routes from manifest
$routes = $manifest_data['php']['routes'] ?? [];
// Try to find matching route
foreach ($routes as $route) {
$pattern = $route['pattern'] ?? '';
$controller = $route['class'] ?? '';
$method = $route['method'] ?? '';
// Simple pattern matching - exact match for now
// TODO: Support route parameters like /users/:id
if ($pattern === $url) {
json_response([
'found' => true,
'controller' => $controller,
'method' => $method,
'pattern' => $pattern,
]);
}
}
// Not found
json_response([
'found' => false,
'url' => $url,
]);
}