Fix unimplemented login route with # prefix
Fix IDE service routing and path normalization Refactor IDE services and add session rotation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -997,6 +997,11 @@ class Debugger
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't output if suppressed (e.g., for IDE service requests)
|
||||
if (defined('SUPPRESS_CONSOLE_DEBUG_OUTPUT') && SUPPRESS_CONSOLE_DEBUG_OUTPUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't output if console HTML is disabled (e.g., for AJAX requests)
|
||||
if (static::$console_html_disabled) {
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace App\RSpade\Core\Providers;
|
||||
|
||||
use Blade;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Log;
|
||||
@@ -297,6 +298,13 @@ class Rsx_Framework_Provider extends ServiceProvider
|
||||
});
|
||||
}
|
||||
|
||||
// Register Request helper macros
|
||||
if (!Request::hasMacro('is_post')) {
|
||||
Request::macro('is_post', function () {
|
||||
return $this->isMethod('post');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle IDE integration domain auto-discovery
|
||||
$this->handle_ide_domain_discovery();
|
||||
}
|
||||
|
||||
193
app/RSpade/Ide/Services/auth.php
Executable file
193
app/RSpade/Ide/Services/auth.php
Executable file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* IDE Service Authentication
|
||||
*
|
||||
* SECURITY-CRITICAL: This file must be executed BEFORE any IDE service logic.
|
||||
* All /_ide/service/* requests must pass through this authentication check.
|
||||
*
|
||||
* Two authentication modes:
|
||||
* 1. Session-based auth with signature verification (production/remote)
|
||||
* 2. Localhost bypass (development only, strict requirements)
|
||||
*/
|
||||
|
||||
// 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_AUTH_BASE_PATH', realpath($system_path . '/..')); // Project root
|
||||
define('IDE_AUTH_SYSTEM_PATH', $system_path); // Framework root
|
||||
|
||||
// Helper to get framework paths
|
||||
function ide_auth_framework_path($relative_path) {
|
||||
return IDE_AUTH_SYSTEM_PATH . '/' . ltrim($relative_path, '/');
|
||||
}
|
||||
|
||||
// JSON response helper
|
||||
function ide_auth_json_response($data, $code = 200) {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Error response helper
|
||||
function ide_auth_error_response($message, $code = 400) {
|
||||
ide_auth_json_response(['success' => false, 'error' => $message], $code);
|
||||
}
|
||||
|
||||
// Check if IDE services are enabled
|
||||
$env_file = IDE_AUTH_BASE_PATH . '/.env';
|
||||
if (file_exists($env_file)) {
|
||||
$env_content = file_get_contents($env_file);
|
||||
// Check if production
|
||||
if (preg_match('/^APP_ENV=production$/m', $env_content)) {
|
||||
// Check if explicitly enabled in production
|
||||
if (!preg_match('/^RSX_IDE_SERVICES_ENABLED=true$/m', $env_content)) {
|
||||
ide_auth_error_response('IDE services disabled in production', 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse request URI to get service
|
||||
$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
$service_path = str_replace('/_ide/service', '', $request_uri);
|
||||
$service_path = trim($service_path, '/');
|
||||
|
||||
// Get request body for signature verification
|
||||
$request_body = file_get_contents('php://input');
|
||||
|
||||
// Handle authentication
|
||||
$auth_data = [];
|
||||
$session_header = $_SERVER['HTTP_X_SESSION'] ?? null;
|
||||
$signature_header = $_SERVER['HTTP_X_SIGNATURE'] ?? null;
|
||||
|
||||
// Check if this is auth/create request (no auth required for session creation)
|
||||
if ($service_path === 'auth/create' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Generate new auth session
|
||||
$session = bin2hex(random_bytes(20));
|
||||
$client_key = bin2hex(random_bytes(20));
|
||||
$server_key = bin2hex(random_bytes(20));
|
||||
|
||||
$auth_data_new = [
|
||||
'session' => $session,
|
||||
'client_key' => $client_key,
|
||||
'server_key' => $server_key,
|
||||
'created' => time()
|
||||
];
|
||||
|
||||
// Save auth file
|
||||
$bridge_dir = ide_auth_framework_path('storage/rsx-ide-bridge');
|
||||
if (!is_dir($bridge_dir)) {
|
||||
mkdir($bridge_dir, 0755, true);
|
||||
}
|
||||
|
||||
// Rotate old sessions - keep max 100 auth files
|
||||
$auth_files = glob($bridge_dir . '/auth-*.json');
|
||||
if (count($auth_files) >= 100) {
|
||||
// Sort by modification time (oldest first)
|
||||
usort($auth_files, function($a, $b) {
|
||||
return filemtime($a) - filemtime($b);
|
||||
});
|
||||
|
||||
// Delete oldest files to get down to 99 (so we can add the new one)
|
||||
$files_to_delete = count($auth_files) - 99;
|
||||
for ($i = 0; $i < $files_to_delete; $i++) {
|
||||
unlink($auth_files[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
$auth_file = $bridge_dir . '/auth-' . $session . '.json';
|
||||
file_put_contents($auth_file, json_encode($auth_data_new, JSON_PRETTY_PRINT));
|
||||
|
||||
// Return auth data (unsigned response since client doesn't have keys yet)
|
||||
http_response_code(200);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'session' => $session,
|
||||
'client_key' => $client_key,
|
||||
'server_key' => $server_key
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Authentication required for all other requests
|
||||
// Handle authentication with localhost bypass
|
||||
|
||||
// Localhost bypass for IDE integration
|
||||
$is_localhost_bypass = false;
|
||||
$request_host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$request_scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$remote_addr = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
|
||||
// Check for proxy headers
|
||||
$has_proxy_headers = false;
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (str_starts_with($key, 'HTTP_X_')) {
|
||||
if ($key === 'HTTP_X_SESSION' || $key === 'HTTP_X_SIGNATURE') {
|
||||
continue;
|
||||
}
|
||||
$has_proxy_headers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if request is from loopback
|
||||
$is_loopback_ip = (
|
||||
!$has_proxy_headers &&
|
||||
($remote_addr === '127.0.0.1' ||
|
||||
$remote_addr === '::1' ||
|
||||
str_starts_with($remote_addr, '127.'))
|
||||
);
|
||||
|
||||
// Check if not in production
|
||||
$is_not_production = true;
|
||||
if (file_exists($env_file)) {
|
||||
$env_content = file_get_contents($env_file);
|
||||
if (preg_match('/^APP_ENV=production$/m', $env_content)) {
|
||||
$is_not_production = false;
|
||||
}
|
||||
}
|
||||
|
||||
// All conditions must be true for bypass
|
||||
if (
|
||||
$request_host === 'localhost' &&
|
||||
$request_scheme === 'http' &&
|
||||
$is_loopback_ip &&
|
||||
$is_not_production
|
||||
) {
|
||||
$is_localhost_bypass = true;
|
||||
$auth_data = [
|
||||
'session' => 'localhost-bypass',
|
||||
'client_key' => '',
|
||||
'server_key' => ''
|
||||
];
|
||||
}
|
||||
|
||||
if (!$is_localhost_bypass) {
|
||||
// Require authentication for non-localhost
|
||||
if (!$session_header || !$signature_header) {
|
||||
ide_auth_error_response('Authentication required', 401);
|
||||
}
|
||||
|
||||
// Load and validate session
|
||||
$auth_file = ide_auth_framework_path('storage/rsx-ide-bridge/auth-' . $session_header . '.json');
|
||||
if (!file_exists($auth_file)) {
|
||||
ide_auth_error_response('Session not found', 401);
|
||||
}
|
||||
|
||||
$auth_data = json_decode(file_get_contents($auth_file), true);
|
||||
$expected_signature = sha1($request_body . $auth_data['client_key']);
|
||||
if ($signature_header !== $expected_signature) {
|
||||
ide_auth_error_response('Invalid signature', 401);
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication passed - store auth data for handlers to use if needed
|
||||
define('IDE_AUTH_PASSED', true);
|
||||
define('IDE_AUTH_DATA', json_encode($auth_data));
|
||||
define('IDE_AUTH_IS_LOCALHOST_BYPASS', $is_localhost_bypass);
|
||||
|
||||
// Suppress console_debug output for IDE service requests
|
||||
// These are programmatic API calls, not user-facing pages
|
||||
define('SUPPRESS_CONSOLE_DEBUG_OUTPUT', true);
|
||||
@@ -11,6 +11,18 @@
|
||||
* 3. Server validates client signature
|
||||
* 4. Server signs response with SHA1(body + server_key)
|
||||
* 5. Client validates server signature
|
||||
*
|
||||
* Localhost Bypass:
|
||||
* Authentication can be bypassed when ALL of the following conditions are met:
|
||||
* 1. Request is to hostname 'localhost' (not 127.0.0.1, not any other domain)
|
||||
* 2. Request is NOT HTTPS (must be http://)
|
||||
* 3. Request comes from a loopback IP address (127.x.x.x or ::1)
|
||||
* 4. No X-* proxy headers present (except X-Session/X-Signature)
|
||||
* 5. Laravel is NOT in production mode
|
||||
*
|
||||
* This allows the VS Code extension to make unauthenticated requests when running
|
||||
* on the same machine as the dev server, eliminating session management overhead
|
||||
* for local development while maintaining security for remote connections.
|
||||
*/
|
||||
|
||||
// Error reporting for development
|
||||
@@ -39,8 +51,34 @@ function storage_path($path = '') {
|
||||
// Load exec_safe() function
|
||||
require_once ide_framework_path('app/RSpade/helpers.php');
|
||||
|
||||
// Helper to normalize file paths for IDE consumption
|
||||
// Framework files (not starting with rsx/) need system/ prefix
|
||||
function normalize_ide_path($path) {
|
||||
if (!$path || str_starts_with($path, 'rsx/')) {
|
||||
return $path;
|
||||
}
|
||||
return 'system/' . $path;
|
||||
}
|
||||
|
||||
// Helper to recursively normalize all 'file' keys in response data
|
||||
function normalize_response_paths($data) {
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $key => $value) {
|
||||
if ($key === 'file' && is_string($value)) {
|
||||
$data[$key] = normalize_ide_path($value);
|
||||
} elseif (is_array($value)) {
|
||||
$data[$key] = normalize_response_paths($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
// JSON response helper
|
||||
function json_response($data, $code = 200) {
|
||||
// Normalize all file paths in response for IDE consumption
|
||||
$data = normalize_response_paths($data);
|
||||
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
$json = json_encode($data);
|
||||
@@ -61,19 +99,14 @@ function error_response($message, $code = 400) {
|
||||
json_response(['success' => false, 'error' => $message], $code);
|
||||
}
|
||||
|
||||
// Check if IDE services are enabled
|
||||
$env_file = IDE_BASE_PATH . '/.env';
|
||||
if (file_exists($env_file)) {
|
||||
$env_content = file_get_contents($env_file);
|
||||
// Check if production
|
||||
if (preg_match('/^APP_ENV=production$/m', $env_content)) {
|
||||
// Check if explicitly enabled in production
|
||||
if (!preg_match('/^RSX_IDE_SERVICES_ENABLED=true$/m', $env_content)) {
|
||||
error_response('IDE services disabled in production', 403);
|
||||
}
|
||||
}
|
||||
// Authentication already handled by auth.php before this file is loaded
|
||||
// Retrieve auth data from constants set by auth.php
|
||||
if (!defined('IDE_AUTH_PASSED')) {
|
||||
error_response('Authentication check did not run - this should never happen', 500);
|
||||
}
|
||||
|
||||
$auth_data = json_decode(IDE_AUTH_DATA, true);
|
||||
|
||||
// Parse request URI to get service
|
||||
$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
$service_path = str_replace('/_ide/service', '', $request_uri);
|
||||
@@ -88,75 +121,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$request_data = array_merge($request_data ?? [], $_GET);
|
||||
}
|
||||
|
||||
// Handle authentication
|
||||
$auth_data = [];
|
||||
$session_header = $_SERVER['HTTP_X_SESSION'] ?? null;
|
||||
$signature_header = $_SERVER['HTTP_X_SIGNATURE'] ?? null;
|
||||
|
||||
if ($session_header && $signature_header) {
|
||||
// Load auth data for session
|
||||
$auth_file = ide_framework_path('storage/rsx-ide-bridge/auth-' . $session_header . '.json');
|
||||
|
||||
if (file_exists($auth_file)) {
|
||||
$auth_data = json_decode(file_get_contents($auth_file), true);
|
||||
|
||||
// Validate signature
|
||||
$expected_signature = sha1($request_body . $auth_data['client_key']);
|
||||
if ($signature_header !== $expected_signature) {
|
||||
error_response('Invalid signature', 401);
|
||||
}
|
||||
} else {
|
||||
// Session not found - provide recovery hint
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'error' => 'Session not found',
|
||||
'code' => 401,
|
||||
'recoverable' => true,
|
||||
'recovery' => 'Create new session via POST /_ide/service/auth/create'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// No auth provided - check if this is an auth creation request
|
||||
if ($service_path === 'auth/create' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Generate new auth session
|
||||
$session = bin2hex(random_bytes(20));
|
||||
$client_key = bin2hex(random_bytes(20));
|
||||
$server_key = bin2hex(random_bytes(20));
|
||||
|
||||
$auth_data = [
|
||||
'session' => $session,
|
||||
'client_key' => $client_key,
|
||||
'server_key' => $server_key,
|
||||
'created' => time()
|
||||
];
|
||||
|
||||
// Save auth file
|
||||
$bridge_dir = ide_framework_path('storage/rsx-ide-bridge');
|
||||
if (!is_dir($bridge_dir)) {
|
||||
mkdir($bridge_dir, 0755, true);
|
||||
}
|
||||
|
||||
$auth_file = $bridge_dir . '/auth-' . $session . '.json';
|
||||
file_put_contents($auth_file, json_encode($auth_data, JSON_PRETTY_PRINT));
|
||||
|
||||
// Return auth data (unsigned response since client doesn't have keys yet)
|
||||
http_response_code(200);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'session' => $session,
|
||||
'client_key' => $client_key,
|
||||
'server_key' => $server_key
|
||||
]);
|
||||
exit;
|
||||
} else {
|
||||
error_response('Authentication required', 401);
|
||||
}
|
||||
}
|
||||
|
||||
// Route to appropriate service handler
|
||||
// This handler handles lightweight services that don't need Laravel bootstrap
|
||||
// All services requiring Manifest/Laravel are handled by laravel_handler.php
|
||||
// Note: auth/create is handled in auth.php before routing
|
||||
switch ($service_path) {
|
||||
case 'format':
|
||||
handle_format_service($request_data);
|
||||
@@ -171,9 +139,14 @@ switch ($service_path) {
|
||||
break;
|
||||
|
||||
case 'exec':
|
||||
case 'command':
|
||||
handle_exec_service($request_data);
|
||||
break;
|
||||
|
||||
case 'resolve_class':
|
||||
handle_resolve_class_service($request_data);
|
||||
break;
|
||||
|
||||
case 'git':
|
||||
handle_git_service($request_data);
|
||||
break;
|
||||
@@ -182,28 +155,12 @@ switch ($service_path) {
|
||||
handle_git_diff_service($request_data);
|
||||
break;
|
||||
|
||||
case 'command':
|
||||
handle_command_service($request_data);
|
||||
break;
|
||||
|
||||
case 'health':
|
||||
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);
|
||||
json_response(['success' => true, 'service' => 'ide-standalone', 'version' => '1.0.0']);
|
||||
break;
|
||||
|
||||
default:
|
||||
error_response('Unknown service: ' . $service_path, 404);
|
||||
error_response('Unknown service: ' . $service_path . ' (standalone handler supports: format, definition, complete, exec, command, resolve_class, git, git/diff)', 404);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -785,68 +742,15 @@ function handle_command_service($data) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resolve_class service - resolve class/view/component definitions
|
||||
* This is a port of the Ide_Helper_Controller::resolve_class() method
|
||||
* Try to resolve identifier as a PHP class
|
||||
*/
|
||||
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)) {
|
||||
function try_resolve_php_class($identifier, $method_name, $find_php_class) {
|
||||
$class_data = $find_php_class($identifier);
|
||||
|
||||
if ($class_data) {
|
||||
if (!$class_data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file_path = $class_data['file'];
|
||||
$line_number = 1;
|
||||
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
|
||||
@@ -907,7 +811,7 @@ function handle_resolve_class_service($data) {
|
||||
|
||||
$response_data = [
|
||||
'found' => true,
|
||||
'type' => 'class',
|
||||
'type' => 'php_class',
|
||||
'file' => $file_path,
|
||||
'line' => $line_number,
|
||||
'metadata' => [
|
||||
@@ -921,15 +825,109 @@ function handle_resolve_class_service($data) {
|
||||
$response_data['method'] = $method_name;
|
||||
}
|
||||
|
||||
json_response($response_data);
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve identifier as a JavaScript class
|
||||
*/
|
||||
function try_resolve_js_class($identifier, $method_name, $files) {
|
||||
foreach ($files as $file_path => $file_data) {
|
||||
// Only check .js files (not .jqhtml)
|
||||
if (str_ends_with($file_path, '.js') && !str_ends_with($file_path, '.jqhtml')) {
|
||||
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
|
||||
|
||||
if (file_exists($absolute_path)) {
|
||||
$content = file_get_contents($absolute_path);
|
||||
|
||||
// Check for JavaScript class (not specifically Jqhtml_Component)
|
||||
if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends\s+([A-Za-z_][A-Za-z0-9_]*)/', $content, $matches)) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Try as RSX blade view
|
||||
if (!$type || $type === 'view' || !preg_match('/Controller$/', $identifier)) {
|
||||
return [
|
||||
'found' => true,
|
||||
'type' => 'js_class',
|
||||
'file' => $file_path,
|
||||
'line' => $line_number,
|
||||
'identifier' => $identifier,
|
||||
'extends' => $matches[1] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve identifier as a jqhtml component class
|
||||
*
|
||||
* Note: This resolver does NOT check inheritance chains. It's a simple file lookup.
|
||||
* VS Code extension should use js_is_subclass_of() RPC to determine if a class
|
||||
* is actually a Jqhtml_Component before choosing this resolver type.
|
||||
*/
|
||||
function try_resolve_jqhtml_class($identifier, $method_name, $files) {
|
||||
// Load manifest to get js_classes index
|
||||
$manifest_file = ide_framework_path('storage/rsx-build/manifest_data.php');
|
||||
if (!file_exists($manifest_file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$manifest_raw = include $manifest_file;
|
||||
$manifest_data = $manifest_raw['data'] ?? $manifest_raw;
|
||||
$js_classes = $manifest_data['js_classes'] ?? [];
|
||||
|
||||
// Check if this class exists in js_classes
|
||||
if (!isset($js_classes[$identifier])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the file path
|
||||
$file_path = $js_classes[$identifier];
|
||||
|
||||
// Find the line number
|
||||
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
|
||||
$line_number = 1;
|
||||
|
||||
if (file_exists($absolute_path)) {
|
||||
$content = file_get_contents($absolute_path);
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
foreach ($lines as $index => $line) {
|
||||
if (preg_match('/class\s+' . preg_quote($identifier, '/') . '\s+extends/', $line)) {
|
||||
$line_number = $index + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'found' => true,
|
||||
'type' => 'jqhtml_class',
|
||||
'file' => $file_path,
|
||||
'line' => $line_number,
|
||||
'identifier' => $identifier,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve identifier as a view
|
||||
*/
|
||||
function try_resolve_view($identifier, $find_view) {
|
||||
$view_data = $find_view($identifier);
|
||||
|
||||
if ($view_data) {
|
||||
if (!$view_data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file_path = $view_data['file'];
|
||||
$line_number = 1;
|
||||
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
|
||||
@@ -947,29 +945,38 @@ function handle_resolve_class_service($data) {
|
||||
}
|
||||
}
|
||||
|
||||
json_response([
|
||||
return [
|
||||
'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)) {
|
||||
/**
|
||||
* Try to resolve identifier as a bundle alias
|
||||
*/
|
||||
function try_resolve_bundle_alias($identifier, $find_php_class) {
|
||||
$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];
|
||||
if (!file_exists($config_path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = include $config_path;
|
||||
if (!isset($config['bundle_aliases'][$identifier])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$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) {
|
||||
if (!$class_data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file_path = $class_data['file'];
|
||||
$line_number = 1;
|
||||
$absolute_path = IDE_BASE_PATH . '/' . $file_path;
|
||||
@@ -986,21 +993,20 @@ function handle_resolve_class_service($data) {
|
||||
}
|
||||
}
|
||||
|
||||
json_response([
|
||||
return [
|
||||
'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') {
|
||||
/**
|
||||
* Try to resolve identifier as a jqhtml template
|
||||
*/
|
||||
function try_resolve_jqhtml_template($identifier, $files, $camel_to_snake, $snake_to_pascal) {
|
||||
$component_snake = $camel_to_snake($identifier);
|
||||
|
||||
foreach ($files as $file_path => $file_data) {
|
||||
@@ -1023,53 +1029,23 @@ function handle_resolve_class_service($data) {
|
||||
}
|
||||
}
|
||||
|
||||
json_response([
|
||||
return [
|
||||
'found' => true,
|
||||
'type' => 'jqhtml_template',
|
||||
'file' => $file_path,
|
||||
'line' => $line_number,
|
||||
'identifier' => $identifier,
|
||||
]);
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
/**
|
||||
* Try to resolve method in jqhtml component
|
||||
*/
|
||||
function try_resolve_jqhtml_method($identifier, $method_name, $files) {
|
||||
$search_method = ($method_name === 'data') ? 'on_load' : $method_name;
|
||||
|
||||
foreach ($files as $file_path => $file_data) {
|
||||
@@ -1105,29 +1081,251 @@ function handle_resolve_class_service($data) {
|
||||
}
|
||||
}
|
||||
|
||||
json_response([
|
||||
return [
|
||||
'found' => true,
|
||||
'type' => 'jqhtml_class_method',
|
||||
'file' => $file_path,
|
||||
'line' => $line_number,
|
||||
'identifier' => $identifier,
|
||||
'method' => $method_name,
|
||||
]);
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resolve_class service - resolve class/view/component definitions
|
||||
*
|
||||
* RESOLUTION TYPE SYSTEM
|
||||
* ======================
|
||||
*
|
||||
* This endpoint resolves identifiers to file locations based on type priority.
|
||||
* It supports CSV type lists for priority-based resolution.
|
||||
*
|
||||
* AVAILABLE TYPES:
|
||||
*
|
||||
* 1. php_class - PHP class files (*.php)
|
||||
* - Searches manifest for PHP classes
|
||||
* - Supports method lookup with fallback to class
|
||||
* - Returns: file path, line number, namespace, extends, fqcn
|
||||
*
|
||||
* 2. js_class - JavaScript class files (*.js, not .jqhtml)
|
||||
* - Searches for JS classes with extends clause
|
||||
* - Does NOT require extending Jqhtml_Component
|
||||
* - Returns: file path, line number, extends
|
||||
*
|
||||
* 3. jqhtml_class - jqhtml component JavaScript files (*.js)
|
||||
* - Searches for classes extending Jqhtml_Component
|
||||
* - Returns: file path, line number
|
||||
*
|
||||
* 4. view - Blade view templates (*.blade.php)
|
||||
* - Searches for @rsx_id directives
|
||||
* - Returns: file path, line number
|
||||
*
|
||||
* 5. bundle_alias - Bundle configuration aliases
|
||||
* - Resolves bundle aliases from config/rsx.php
|
||||
* - Returns: bundle class file location
|
||||
*
|
||||
* 6. jqhtml_template - jqhtml template files (*.jqhtml)
|
||||
* - Searches for <Define:ComponentName> tags
|
||||
* - Returns: template file path, line number
|
||||
*
|
||||
* 7. jqhtml_class_method - Methods in jqhtml component classes
|
||||
* - Requires both identifier and method parameters
|
||||
* - Searches within Jqhtml_Component subclasses
|
||||
* - Returns: file path, method line number
|
||||
*
|
||||
* CSV TYPE LISTS:
|
||||
* ===============
|
||||
*
|
||||
* The type parameter accepts comma-separated lists for priority-based resolution.
|
||||
* The server tries each type in order and returns the first match found.
|
||||
*
|
||||
* Examples:
|
||||
* - type=php_class,js_class
|
||||
* Try PHP classes first, then JavaScript classes
|
||||
* Use case: From PHP files (always want PHP), fallback to JS
|
||||
*
|
||||
* - type=js_class,php_class
|
||||
* Try JavaScript classes first, then PHP classes
|
||||
* Use case: From JS files (component inheritance), fallback to PHP controllers
|
||||
*
|
||||
* - type=jqhtml_class,js_class
|
||||
* Try jqhtml components first, then general JS classes
|
||||
* Use case: jqhtml extends="" attribute resolution
|
||||
*
|
||||
* METHOD RESOLUTION:
|
||||
* ==================
|
||||
*
|
||||
* When the method parameter is provided, the resolver attempts to find the specific
|
||||
* method within the class. If the method exists, returns its line number. If the
|
||||
* method doesn't exist but the class does, returns the class line number as fallback.
|
||||
*
|
||||
* This allows graceful degradation: clicking on Controller.missing_method() will
|
||||
* navigate to the Controller class even if missing_method() doesn't exist.
|
||||
*
|
||||
* USAGE FROM VS CODE:
|
||||
* ===================
|
||||
*
|
||||
* The VS Code extension determines which types to use based on context:
|
||||
*
|
||||
* - Route patterns → php_class (routes are always PHP)
|
||||
* - PHP class references → php_class (PHP files only reference PHP)
|
||||
* - JS class references → js_class,php_class (try JS first, then PHP controllers)
|
||||
* - jqhtml extends="" → jqhtml_class,js_class (component inheritance)
|
||||
* - jqhtml $xxx=Class.method → js_class,php_class (components or controllers)
|
||||
* - jqhtml $xxx=this.method → jqhtml_class_method (current component only)
|
||||
*
|
||||
* PARAMETERS:
|
||||
* - identifier/class (required): The class/view/component name to resolve
|
||||
* - method (optional): Method name within the class
|
||||
* - type (optional): Single type or CSV list of types to try in order
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
// Support CSV type list for priority ordering
|
||||
$type_list = [];
|
||||
if ($type) {
|
||||
$type_list = array_map('trim', explode(',', $type));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Only match PHP files - check file extension and presence of PHP-specific metadata
|
||||
if (isset($file_data['class']) && $file_data['class'] === $class_name) {
|
||||
// Must be a PHP file (not .js, not .jqhtml)
|
||||
if (str_ends_with($file_path, '.php')) {
|
||||
// Add file path to the data so caller knows which file it came from
|
||||
$file_data['file'] = $file_path;
|
||||
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));
|
||||
};
|
||||
|
||||
// If no type list specified, try auto-detection with legacy 'class' type
|
||||
if (empty($type_list)) {
|
||||
if (!$type || $type === 'class' || preg_match('/^[A-Z][A-Za-z0-9_]*$/', $identifier)) {
|
||||
// Legacy: Try PHP class with auto-detection fallback
|
||||
$type_list = ['php_class', 'view', 'bundle_alias', 'jqhtml_template', 'jqhtml_class'];
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found
|
||||
// Try each type in priority order
|
||||
foreach ($type_list as $current_type) {
|
||||
$result = null;
|
||||
|
||||
switch ($current_type) {
|
||||
case 'php_class':
|
||||
$result = try_resolve_php_class($identifier, $method_name, $find_php_class);
|
||||
break;
|
||||
|
||||
case 'js_class':
|
||||
$result = try_resolve_js_class($identifier, $method_name, $files);
|
||||
break;
|
||||
|
||||
case 'jqhtml_class':
|
||||
$result = try_resolve_jqhtml_class($identifier, $method_name, $files);
|
||||
break;
|
||||
|
||||
case 'view':
|
||||
$result = try_resolve_view($identifier, $find_view);
|
||||
break;
|
||||
|
||||
case 'bundle_alias':
|
||||
$result = try_resolve_bundle_alias($identifier, $find_php_class);
|
||||
break;
|
||||
|
||||
case 'jqhtml_template':
|
||||
$result = try_resolve_jqhtml_template($identifier, $files, $camel_to_snake, $snake_to_pascal);
|
||||
break;
|
||||
|
||||
case 'jqhtml_class_method':
|
||||
if ($method_name) {
|
||||
$result = try_resolve_jqhtml_method($identifier, $method_name, $files);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If we found a result, return it immediately
|
||||
if ($result && ($result['found'] ?? false)) {
|
||||
json_response($result);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found after trying all types
|
||||
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'],
|
||||
'searched_types' => $type_list,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* OBSOLETE LEGACY CODE REMOVED
|
||||
* =============================
|
||||
* The code below (approximately lines 1410-1685) was the old resolution system
|
||||
* that tried each type individually without CSV support. It has been replaced
|
||||
* by the new CSV type list system above with dedicated resolver functions.
|
||||
*
|
||||
* Removed sections:
|
||||
* - Legacy PHP class fallback (duplicate of try_resolve_php_class)
|
||||
* - Legacy view resolution (duplicate of try_resolve_view)
|
||||
* - Legacy bundle alias resolution (duplicate of try_resolve_bundle_alias)
|
||||
* - Legacy jqhtml template resolution (duplicate of try_resolve_jqhtml_template)
|
||||
* - Legacy jqhtml class resolution (duplicate of try_resolve_jqhtml_class)
|
||||
* - Legacy jqhtml method resolution (duplicate of try_resolve_jqhtml_method)
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Handle js_lineage service - get JavaScript class inheritance chain
|
||||
* This is a port of the Ide_Helper_Controller::js_lineage() method
|
||||
@@ -1201,6 +1399,155 @@ function handle_js_lineage_service($data) {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle js_is_subclass_of service - check if JS class extends another
|
||||
*
|
||||
* This is a direct RPC wrapper around Manifest::js_is_subclass_of()
|
||||
*
|
||||
* PARAMETERS:
|
||||
* - subclass (required): The potential subclass name
|
||||
* - superclass (required): The potential superclass name
|
||||
*
|
||||
* RETURNS:
|
||||
* - is_subclass: boolean - true if subclass extends superclass anywhere in chain
|
||||
*/
|
||||
function handle_js_is_subclass_of_service($data) {
|
||||
$subclass = $data['subclass'] ?? null;
|
||||
$superclass = $data['superclass'] ?? null;
|
||||
|
||||
if (!$subclass || !$superclass) {
|
||||
json_response([
|
||||
'error' => 'Missing required parameters: subclass and superclass',
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Load manifest
|
||||
$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;
|
||||
$js_classes = $manifest_data['js_classes'] ?? [];
|
||||
$files = $manifest_data['files'] ?? [];
|
||||
|
||||
// Implement same logic as Manifest::js_is_subclass_of
|
||||
$current_class = $subclass;
|
||||
$visited = [];
|
||||
|
||||
while ($current_class) {
|
||||
// Prevent infinite loops
|
||||
if (in_array($current_class, $visited)) {
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
|
||||
$visited[] = $current_class;
|
||||
|
||||
// Find the current class in the manifest
|
||||
if (!isset($js_classes[$current_class])) {
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
|
||||
// Get file metadata
|
||||
$file_path = $js_classes[$current_class];
|
||||
$metadata = $files[$file_path] ?? null;
|
||||
|
||||
if (!$metadata || empty($metadata['extends'])) {
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
|
||||
if ($metadata['extends'] == $superclass) {
|
||||
json_response(['is_subclass' => true]);
|
||||
}
|
||||
|
||||
// Move up the chain to the parent class
|
||||
$current_class = $metadata['extends'];
|
||||
}
|
||||
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle php_is_subclass_of service - check if PHP class extends another
|
||||
*
|
||||
* This is a direct RPC wrapper around Manifest::php_is_subclass_of()
|
||||
*
|
||||
* PARAMETERS:
|
||||
* - subclass (required): The potential subclass name
|
||||
* - superclass (required): The potential superclass name
|
||||
*
|
||||
* RETURNS:
|
||||
* - is_subclass: boolean - true if subclass extends superclass anywhere in chain
|
||||
*/
|
||||
function handle_php_is_subclass_of_service($data) {
|
||||
$subclass = $data['subclass'] ?? null;
|
||||
$superclass = $data['superclass'] ?? null;
|
||||
|
||||
if (!$subclass || !$superclass) {
|
||||
json_response([
|
||||
'error' => 'Missing required parameters: subclass and superclass',
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Load manifest
|
||||
$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'] ?? [];
|
||||
|
||||
// Implement same logic as Manifest::php_is_subclass_of
|
||||
$current_class = $subclass;
|
||||
$visited = [];
|
||||
|
||||
while ($current_class) {
|
||||
// Prevent infinite loops
|
||||
if (in_array($current_class, $visited)) {
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
|
||||
$visited[] = $current_class;
|
||||
|
||||
// Find the current class in files
|
||||
$found = false;
|
||||
foreach ($files as $file_path => $file_data) {
|
||||
if (isset($file_data['class']) && $file_data['class'] === $current_class) {
|
||||
// Must be a PHP file
|
||||
if (str_ends_with($file_path, '.php')) {
|
||||
$extends = $file_data['extends'] ?? null;
|
||||
|
||||
if (!$extends) {
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
|
||||
// Normalize the extends class name (strip namespace)
|
||||
$extends_parts = explode('\\', $extends);
|
||||
$extends_simple = end($extends_parts);
|
||||
|
||||
if ($extends_simple == $superclass) {
|
||||
json_response(['is_subclass' => true]);
|
||||
}
|
||||
|
||||
// Move up the chain
|
||||
$current_class = $extends_simple;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
json_response(['is_subclass' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resolve_url service - resolve URL to controller/method
|
||||
* Takes a URL path and returns the controller and method that handles it
|
||||
@@ -1251,3 +1598,4 @@ function handle_resolve_url_service($data) {
|
||||
'url' => $url,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
192
app/RSpade/Ide/Services/laravel_handler.php
Executable file
192
app/RSpade/Ide/Services/laravel_handler.php
Executable file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* IDE Service Handler (Laravel-based)
|
||||
*
|
||||
* Handles all IDE service requests that require Laravel/Manifest access.
|
||||
* Format-only services use the standalone handler.php instead.
|
||||
*
|
||||
* This handler bootstraps Laravel to access Manifest functions directly,
|
||||
* eliminating duplicate logic and ensuring single source of truth.
|
||||
*
|
||||
* Authentication: Same localhost bypass logic as standalone handler
|
||||
* See handler.php for authentication documentation.
|
||||
*/
|
||||
|
||||
// Error reporting for development
|
||||
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
|
||||
|
||||
// Helper to get framework paths
|
||||
function ide_framework_path($relative_path) {
|
||||
return IDE_SYSTEM_PATH . '/' . ltrim($relative_path, '/');
|
||||
}
|
||||
|
||||
// Bootstrap Laravel
|
||||
require_once IDE_SYSTEM_PATH . '/vendor/autoload.php';
|
||||
$app = require_once IDE_SYSTEM_PATH . '/bootstrap/app.php';
|
||||
|
||||
// We don't need to handle HTTP requests through Laravel's kernel
|
||||
// Just boot the application so we can use Manifest
|
||||
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
|
||||
// JSON response helper
|
||||
function json_response($data, $code = 200) {
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Error response helper
|
||||
function error_response($message, $code = 400) {
|
||||
json_response(['success' => false, 'error' => $message], $code);
|
||||
}
|
||||
|
||||
// Authentication already handled by auth.php before this file is loaded
|
||||
// Retrieve auth data from constants set by auth.php
|
||||
if (!defined('IDE_AUTH_PASSED')) {
|
||||
error_response('Authentication check did not run - this should never happen', 500);
|
||||
}
|
||||
|
||||
$auth_data = json_decode(IDE_AUTH_DATA, true);
|
||||
|
||||
// Parse request URI to get service
|
||||
$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
$service_path = str_replace('/_ide/service', '', $request_uri);
|
||||
$service_path = trim($service_path, '/');
|
||||
|
||||
// Get request body
|
||||
$request_body = file_get_contents('php://input');
|
||||
$request_data = json_decode($request_body, true);
|
||||
|
||||
// Merge GET parameters (for backward compatibility)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
$request_data = array_merge($request_data ?? [], $_GET);
|
||||
}
|
||||
|
||||
// Route to appropriate service handler
|
||||
// This handler provides Laravel-based services (can access Manifest, models, etc.)
|
||||
// Format service is handled by handler.php (standalone, no Laravel)
|
||||
switch ($service_path) {
|
||||
case 'manifest_build':
|
||||
handle_manifest_build_service($request_data);
|
||||
break;
|
||||
|
||||
case 'js_is_subclass_of':
|
||||
handle_js_is_subclass_of_service($request_data);
|
||||
break;
|
||||
|
||||
case 'php_is_subclass_of':
|
||||
handle_php_is_subclass_of_service($request_data);
|
||||
break;
|
||||
|
||||
case 'resolve_class':
|
||||
// TODO: Migrate from handler.php and refactor to use Manifest directly
|
||||
error_response('Service not yet migrated to Laravel handler', 501);
|
||||
break;
|
||||
|
||||
case 'definition':
|
||||
// TODO: Migrate from handler.php and refactor to use Manifest directly
|
||||
error_response('Service not yet migrated to Laravel handler', 501);
|
||||
break;
|
||||
|
||||
case 'complete':
|
||||
// TODO: Migrate from handler.php and refactor to use Manifest directly
|
||||
error_response('Service not yet migrated to Laravel handler', 501);
|
||||
break;
|
||||
|
||||
case 'exec':
|
||||
case 'command':
|
||||
// TODO: Migrate from handler.php
|
||||
error_response('Service not yet migrated to Laravel handler', 501);
|
||||
break;
|
||||
|
||||
// git and git/diff services are handled by handler.php (standalone, no Laravel needed)
|
||||
|
||||
case 'js_lineage':
|
||||
// TODO: Migrate from handler.php - or remove if js_is_subclass_of replaces it
|
||||
error_response('Service not yet migrated to Laravel handler', 501);
|
||||
break;
|
||||
|
||||
case 'resolve_url':
|
||||
// TODO: Migrate from handler.php and refactor to use Manifest directly
|
||||
error_response('Service not yet migrated to Laravel handler', 501);
|
||||
break;
|
||||
|
||||
case 'health':
|
||||
json_response(['success' => true, 'service' => 'ide-laravel', 'version' => '1.0.0']);
|
||||
break;
|
||||
|
||||
default:
|
||||
error_response('Unknown service: ' . $service_path, 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle manifest_build service - trigger incremental manifest update
|
||||
*
|
||||
* Calls Manifest::init() which performs incremental update if needed.
|
||||
* Does NOT clear the manifest, just updates changed files.
|
||||
*/
|
||||
function handle_manifest_build_service($data) {
|
||||
try {
|
||||
// Manifest::init() will do incremental update if needed
|
||||
\App\RSpade\Core\Manifest\Manifest::init();
|
||||
json_response(['success' => true]);
|
||||
} catch (\Exception $e) {
|
||||
// Return error but VS Code extension will suppress user notification
|
||||
json_response(['success' => false, 'error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle js_is_subclass_of service - check if JS class extends another
|
||||
*
|
||||
* Direct RPC wrapper around Manifest::js_is_subclass_of()
|
||||
*/
|
||||
function handle_js_is_subclass_of_service($data) {
|
||||
$subclass = $data['subclass'] ?? null;
|
||||
$superclass = $data['superclass'] ?? null;
|
||||
|
||||
if (!$subclass || !$superclass) {
|
||||
json_response([
|
||||
'error' => 'Missing required parameters: subclass and superclass',
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$is_subclass = \App\RSpade\Core\Manifest\Manifest::js_is_subclass_of($subclass, $superclass);
|
||||
json_response(['is_subclass' => $is_subclass]);
|
||||
} catch (\Exception $e) {
|
||||
error_response('Manifest error: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle php_is_subclass_of service - check if PHP class extends another
|
||||
*
|
||||
* Direct RPC wrapper around Manifest::php_is_subclass_of()
|
||||
*/
|
||||
function handle_php_is_subclass_of_service($data) {
|
||||
$subclass = $data['subclass'] ?? null;
|
||||
$superclass = $data['superclass'] ?? null;
|
||||
|
||||
if (!$subclass || !$superclass) {
|
||||
json_response([
|
||||
'error' => 'Missing required parameters: subclass and superclass',
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$is_subclass = \App\RSpade\Core\Manifest\Manifest::php_is_subclass_of($subclass, $superclass);
|
||||
json_response(['is_subclass' => $is_subclass]);
|
||||
} catch (\Exception $e) {
|
||||
error_response('Manifest error: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ exports.AutoRenameProvider = void 0;
|
||||
const vscode = __importStar(require("vscode"));
|
||||
const path = __importStar(require("path"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const ide_bridge_client_1 = require("./ide_bridge_client");
|
||||
/**
|
||||
* Provides automatic file renaming based on RSX naming conventions
|
||||
*
|
||||
@@ -43,6 +44,7 @@ class AutoRenameProvider {
|
||||
this.config_enabled = false;
|
||||
this.workspace_root = '';
|
||||
this.is_checking = false;
|
||||
this.ide_bridge_client = null;
|
||||
this.init();
|
||||
}
|
||||
find_rspade_root() {
|
||||
@@ -327,9 +329,24 @@ class AutoRenameProvider {
|
||||
return class_name.toLowerCase() + '.php';
|
||||
}
|
||||
async get_suggested_js_filename(file_path, class_name, content) {
|
||||
// Check if this extends Jqhtml_Component
|
||||
const is_jqhtml = content.includes('extends Jqhtml_Component') ||
|
||||
content.match(/extends\s+[A-Za-z0-9_]+\s+extends Jqhtml_Component/);
|
||||
// Check if this extends Jqhtml_Component (directly or via inheritance)
|
||||
let is_jqhtml = content.includes('extends Jqhtml_Component');
|
||||
// If not directly extending, check via API
|
||||
if (!is_jqhtml && content.includes('extends ')) {
|
||||
// Initialize IDE bridge client if needed
|
||||
if (!this.ide_bridge_client) {
|
||||
const output_channel = vscode.window.createOutputChannel('RSpade Auto Rename');
|
||||
this.ide_bridge_client = new ide_bridge_client_1.IdeBridgeClient(output_channel);
|
||||
}
|
||||
try {
|
||||
const response = await this.ide_bridge_client.js_is_subclass_of(class_name, 'Jqhtml_Component');
|
||||
is_jqhtml = response.is_subclass || false;
|
||||
}
|
||||
catch (error) {
|
||||
// If API call fails, fall back to direct check only
|
||||
console.log('[AutoRename] JS - Failed to check inheritance:', error);
|
||||
}
|
||||
}
|
||||
console.log('[AutoRename] JS - Is Jqhtml_Component:', is_jqhtml);
|
||||
const dir = path.dirname(file_path);
|
||||
const relative_dir = path.relative(this.workspace_root, dir);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,77 @@
|
||||
"use strict";
|
||||
/**
|
||||
* RSpade Definition Provider - "Go to Definition" for RSX Classes, Routes, and Components
|
||||
*
|
||||
* RESOLUTION TYPE PRIORITY MATRIX
|
||||
* ================================
|
||||
*
|
||||
* This provider determines what to navigate to when you click "Go to Definition" on various
|
||||
* identifiers across different file types. The resolution logic uses CSV type lists sent to
|
||||
* the server endpoint `/_ide/service/resolve_class?type=X,Y,Z` which tries each type in order.
|
||||
*
|
||||
* FILE TYPE HANDLERS & RESOLUTION RULES:
|
||||
*
|
||||
* 1. ROUTE PATTERNS (all files)
|
||||
* Pattern: Rsx::Route('Controller') or Rsx.Route('Controller', 'method')
|
||||
* Type: 'php_class'
|
||||
* Reason: Routes always point to PHP controllers (server-side)
|
||||
*
|
||||
* 2. HREF PATTERNS (Blade, jqhtml)
|
||||
* Pattern: href="/"
|
||||
* Type: 'php_class'
|
||||
* Reason: Resolves URL to controller, always PHP
|
||||
*
|
||||
* 3. JQHTML EXTENDS ATTRIBUTE (jqhtml only)
|
||||
* Pattern: <Define:My_Component extends="DataGrid_Abstract">
|
||||
* Type: 'jqhtml_class,js_class'
|
||||
* Reason: Component inheritance - try jqhtml component first, then JS class
|
||||
*
|
||||
* 4. JQHTML $xxx ATTRIBUTES (jqhtml only)
|
||||
* Pattern: $data_source=Frontend_Controller.fetch_data
|
||||
* Type: 'js_class,php_class'
|
||||
* Reason: Try JS class first (for components), then PHP (for controllers/models)
|
||||
*
|
||||
* Pattern: $handler=this.on_click
|
||||
* Type: 'jqhtml_class_method'
|
||||
* Special: Resolves to current component's method
|
||||
*
|
||||
* 5. THIS REFERENCES (jqhtml only)
|
||||
* Pattern: <%= this.data.users %>
|
||||
* Type: 'jqhtml_class_method'
|
||||
* Reason: Always current component's method/property
|
||||
*
|
||||
* 6. JAVASCRIPT CLASS REFERENCES (JS, jqhtml)
|
||||
* Pattern: class My_Component extends DataGrid_Abstract
|
||||
* Pattern: User_Controller.fetch_all()
|
||||
* Type: 'js_class,php_class'
|
||||
* Reason: Try JS first (component inheritance), then PHP (controllers/models)
|
||||
* Note: JS stub files (auto-generated from PHP) are client-side only, not in manifest
|
||||
*
|
||||
* 7. PHP CLASS REFERENCES (PHP, Blade)
|
||||
* Pattern: class Contacts_DataGrid extends DataGrid_Abstract
|
||||
* Pattern: use Rsx\Lib\DataGrid_QueryBuilder;
|
||||
* Type: 'php_class'
|
||||
* Reason: In PHP files, class references are always PHP (not JavaScript)
|
||||
*
|
||||
* 8. BUNDLE ALIASES (PHP only)
|
||||
* Pattern: 'include' => ['jqhtml', 'frontend']
|
||||
* Type: 'bundle_alias'
|
||||
* Reason: Resolves to bundle class definition
|
||||
*
|
||||
* 9. VIEW REFERENCES (PHP, Blade)
|
||||
* Pattern: @rsx_extends('frontend.layout')
|
||||
* Pattern: rsx_view('frontend.dashboard')
|
||||
* Type: 'view'
|
||||
* Reason: Resolves to Blade view template files
|
||||
*
|
||||
* METHOD RESOLUTION:
|
||||
* When a pattern includes a method (e.g., Controller.method), the server attempts to find
|
||||
* the specific method in the class. If the method isn't found but the class is, it returns
|
||||
* the class location as a fallback.
|
||||
*
|
||||
* IMPORTANT: The server endpoint supports CSV type lists for priority ordering.
|
||||
* Example: type='php_class,js_class' tries PHP first, then JavaScript.
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
@@ -88,8 +161,19 @@ class RspadeDefinitionProvider {
|
||||
return hrefResult;
|
||||
}
|
||||
}
|
||||
// Handle "this.xxx" references in .jqhtml files (highest priority for jqhtml files)
|
||||
// Handle jqhtml-specific patterns
|
||||
if (fileName.endsWith('.jqhtml')) {
|
||||
// Check for extends="ClassName" attribute
|
||||
const extendsResult = await this.handleJqhtmlExtends(document, position);
|
||||
if (extendsResult) {
|
||||
return extendsResult;
|
||||
}
|
||||
// Check for $xxx=... attributes (must come before handleThisReference)
|
||||
const attrResult = await this.handleJqhtmlAttribute(document, position);
|
||||
if (attrResult) {
|
||||
return attrResult;
|
||||
}
|
||||
// Handle "this.xxx" references in template expressions
|
||||
const thisResult = await this.handleThisReference(document, position);
|
||||
if (thisResult) {
|
||||
return thisResult;
|
||||
@@ -132,6 +216,9 @@ class RspadeDefinitionProvider {
|
||||
* - Rsx::Route('Controller', 'method') (PHP)
|
||||
* - Rsx.Route('Controller') (JavaScript, defaults to 'index')
|
||||
* - Rsx.Route('Controller', 'method') (JavaScript)
|
||||
*
|
||||
* Resolution: Routes always point to PHP controllers (server-side)
|
||||
* Type: 'php_class'
|
||||
*/
|
||||
async handleRoutePattern(document, position) {
|
||||
const line = document.lineAt(position.line).text;
|
||||
@@ -148,13 +235,13 @@ class RspadeDefinitionProvider {
|
||||
// Always go to the method when clicking anywhere in Route()
|
||||
// This takes precedence over individual class name lookups
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, method, 'class');
|
||||
const result = await this.queryIdeHelper(controller, method, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error) {
|
||||
// If method lookup fails, try just the controller
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'class');
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error2) {
|
||||
@@ -178,13 +265,13 @@ class RspadeDefinitionProvider {
|
||||
// Single parameter - default to 'index'
|
||||
const method = 'index';
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, method, 'class');
|
||||
const result = await this.queryIdeHelper(controller, method, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error) {
|
||||
// If method lookup fails, try just the controller
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'class');
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error2) {
|
||||
@@ -215,8 +302,8 @@ class RspadeDefinitionProvider {
|
||||
// Query IDE bridge to resolve "/" URL to route
|
||||
const result = await this.ide_bridge.queryUrl('/');
|
||||
if (result && result.found && result.controller && result.method) {
|
||||
// Resolved to controller/method - navigate to it
|
||||
const phpResult = await this.queryIdeHelper(result.controller, result.method, 'class');
|
||||
// Resolved to controller/method - navigate to it (always PHP)
|
||||
const phpResult = await this.queryIdeHelper(result.controller, result.method, 'php_class');
|
||||
return this.createLocationFromResult(phpResult);
|
||||
}
|
||||
}
|
||||
@@ -227,6 +314,106 @@ class RspadeDefinitionProvider {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Handle jqhtml extends="" attribute
|
||||
* Detects patterns like:
|
||||
* - <Define:My_Component extends="DataGrid_Abstract">
|
||||
*
|
||||
* Resolution: Try jqhtml component first, then JS class
|
||||
* Type: 'jqhtml_class,js_class'
|
||||
*/
|
||||
async handleJqhtmlExtends(document, position) {
|
||||
const line = document.lineAt(position.line).text;
|
||||
// Match extends="ClassName" or extends='ClassName'
|
||||
const extendsPattern = /extends\s*=\s*(['"])([A-Z][A-Za-z0-9_]*)\1/g;
|
||||
let match;
|
||||
while ((match = extendsPattern.exec(line)) !== null) {
|
||||
const className = match[2];
|
||||
const classStart = match.index + match[0].indexOf(className);
|
||||
const classEnd = classStart + className.length;
|
||||
// Check if cursor is on the class name
|
||||
if (position.character >= classStart && position.character < classEnd) {
|
||||
try {
|
||||
// Try jqhtml component first, then JS class
|
||||
const result = await this.queryIdeHelper(className, undefined, 'jqhtml_class,js_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error resolving jqhtml extends:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Handle jqhtml $xxx=... attributes
|
||||
* Detects patterns like:
|
||||
* - $data_source=Frontend_Controller.fetch_data
|
||||
* - $on_click=this.handle_click
|
||||
*
|
||||
* Resolution logic:
|
||||
* - If starts with "this.", resolve to current component's jqhtml class methods
|
||||
* - Otherwise, resolve like JS class references: 'js_class,php_class'
|
||||
*/
|
||||
async handleJqhtmlAttribute(document, position) {
|
||||
const line = document.lineAt(position.line).text;
|
||||
// Match $attribute=Value or $attribute=this.method or $attribute=Class.method
|
||||
// Pattern: $word=(this.)?(Word)(.word)?
|
||||
const attrPattern = /\$[a-z_][a-z0-9_]*\s*=\s*(this\.)?([A-Z][A-Za-z0-9_]*)(?:\.([a-z_][a-z0-9_]*))?/gi;
|
||||
let match;
|
||||
while ((match = attrPattern.exec(line)) !== null) {
|
||||
const hasThis = !!match[1]; // "this." prefix
|
||||
const className = match[2];
|
||||
const methodName = match[3]; // Optional method after dot
|
||||
const classStart = match.index + match[0].indexOf(className);
|
||||
const classEnd = classStart + className.length;
|
||||
// Check if cursor is on the class name
|
||||
if (position.character >= classStart && position.character < classEnd) {
|
||||
if (hasThis) {
|
||||
// this.method - resolve to current component's methods
|
||||
// Get the component name from the file
|
||||
let componentName;
|
||||
const fullText = document.getText();
|
||||
const defineMatch = fullText.match(/<Define:([A-Z][A-Za-z0-9_]*)/);
|
||||
if (defineMatch) {
|
||||
componentName = defineMatch[1];
|
||||
}
|
||||
else {
|
||||
// If no Define tag, try to get component name from filename
|
||||
const fileName = document.fileName;
|
||||
const baseName = fileName.split('/').pop()?.replace('.jqhtml', '') || '';
|
||||
if (baseName) {
|
||||
// Convert snake_case to PascalCase with underscores
|
||||
componentName = baseName.split('_').map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('_');
|
||||
}
|
||||
}
|
||||
if (!componentName) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
// The className here is actually the method name after "this."
|
||||
// We need to use the component name as the identifier
|
||||
const result = await this.queryIdeHelper(componentName, className.toLowerCase(), 'jqhtml_class_method');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error resolving jqhtml this reference:', error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Class.method or Class - resolve like JS class references
|
||||
try {
|
||||
const result = await this.queryIdeHelper(className, methodName, 'js_class,php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error resolving jqhtml attribute class:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Handle "this.xxx" references in .jqhtml files
|
||||
* Only handles patterns where cursor is on a word after "this."
|
||||
@@ -340,6 +527,19 @@ class RspadeDefinitionProvider {
|
||||
console.log('[JQHTML Component] Returning location:', component_def.uri.fsPath, 'at position', component_def.position);
|
||||
return new vscode.Location(component_def.uri, component_def.position);
|
||||
}
|
||||
/**
|
||||
* Handle JavaScript class references in .js and .jqhtml files
|
||||
* Detects patterns like:
|
||||
* - class My_Component extends DataGrid_Abstract
|
||||
* - User_Controller.fetch_all()
|
||||
* - await Product_Model.fetch(123)
|
||||
*
|
||||
* Resolution: Try JS classes first (for component inheritance), then PHP classes (for controllers/models)
|
||||
* Type: 'js_class,php_class'
|
||||
*
|
||||
* Note: JS stub files (auto-generated from PHP) are client-side only and not in the manifest,
|
||||
* so there's no conflict - the server will correctly return PHP classes when they exist.
|
||||
*/
|
||||
async handleJavaScriptDefinition(document, position) {
|
||||
// Get the word at the current position
|
||||
const wordRange = document.getWordRangeAtPosition(position, /[A-Z][A-Za-z0-9_]*/);
|
||||
@@ -361,8 +561,9 @@ class RspadeDefinitionProvider {
|
||||
method_name = methodMatch[1];
|
||||
}
|
||||
// Query the IDE helper endpoint
|
||||
// Try JS classes first (component inheritance), then PHP (controllers/models)
|
||||
try {
|
||||
const result = await this.queryIdeHelper(word, method_name, 'class');
|
||||
const result = await this.queryIdeHelper(word, method_name, 'js_class,php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error) {
|
||||
@@ -488,7 +689,9 @@ class RspadeDefinitionProvider {
|
||||
// Check if this looks like an RSX class name
|
||||
if (word.includes('_') && /^[A-Z]/.test(word)) {
|
||||
try {
|
||||
const result = await this.queryIdeHelper(word, undefined, 'class');
|
||||
// When resolving from PHP files, only look for PHP classes
|
||||
// This prevents jumping to JavaScript files when clicking on PHP class references
|
||||
const result = await this.queryIdeHelper(word, undefined, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
}
|
||||
catch (error) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
* Centralized client for communicating with RSpade framework IDE helper endpoints.
|
||||
*
|
||||
* AUTO-DISCOVERY SYSTEM:
|
||||
* 1. Server creates storage/rsx-ide-bridge/domain.txt on first web request
|
||||
* 1. Server creates system/storage/rsx-ide-bridge/domain.txt on first web request
|
||||
* 2. Client reads domain.txt to discover server URL
|
||||
* 3. Falls back to VS Code setting: rspade.serverUrl
|
||||
* 4. Auto-retries with refreshed URL on connection failure
|
||||
@@ -128,6 +128,44 @@ class IdeBridgeClient {
|
||||
async queryUrl(url) {
|
||||
return this.request('/_ide/service/resolve_url', { url }, 'GET');
|
||||
}
|
||||
/**
|
||||
* Check if a JavaScript class extends another class (anywhere in the inheritance chain)
|
||||
*
|
||||
* @param subclass The potential subclass name
|
||||
* @param superclass The potential superclass name
|
||||
* @returns Promise with { is_subclass: boolean }
|
||||
*/
|
||||
async js_is_subclass_of(subclass, superclass) {
|
||||
return this.request('/_ide/service/js_is_subclass_of', { subclass, superclass }, 'GET');
|
||||
}
|
||||
/**
|
||||
* Check if a PHP class extends another class (anywhere in the inheritance chain)
|
||||
*
|
||||
* @param subclass The potential subclass name
|
||||
* @param superclass The potential superclass name
|
||||
* @returns Promise with { is_subclass: boolean }
|
||||
*/
|
||||
async php_is_subclass_of(subclass, superclass) {
|
||||
return this.request('/_ide/service/php_is_subclass_of', { subclass, superclass }, 'GET');
|
||||
}
|
||||
/**
|
||||
* Trigger incremental manifest build
|
||||
*
|
||||
* Calls Manifest::init() on the server to update the manifest cache.
|
||||
* Does NOT clear the manifest, just performs incremental update of changed files.
|
||||
*
|
||||
* @returns Promise with { success: boolean }
|
||||
*/
|
||||
async manifest_build() {
|
||||
try {
|
||||
return await this.request('/_ide/service/manifest_build', {}, 'GET');
|
||||
}
|
||||
catch (error) {
|
||||
// Log to console but don't throw - errors are silent to user
|
||||
console.warn('[IdeBridge] Manifest build failed:', error.message);
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
async make_request_with_retry(endpoint, data, method, retry_count) {
|
||||
if (retry_count > 0) {
|
||||
this.output_channel.appendLine(`\n--- RETRY ATTEMPT ${retry_count} ---`);
|
||||
@@ -146,9 +184,14 @@ class IdeBridgeClient {
|
||||
// Only retry once
|
||||
if (retry_count === 0) {
|
||||
const error_msg = error.message || '';
|
||||
// Session expired or signature invalid - recreate session
|
||||
if (error_msg.includes('Session not found') || error_msg.includes('Invalid signature')) {
|
||||
this.output_channel.appendLine('Session/signature error, recreating session...');
|
||||
// Authentication failure - recreate session
|
||||
// Handles: "Session not found", "Invalid signature", "Authentication required"
|
||||
// or any HTTP 401 response
|
||||
if (error_msg.includes('Session not found') ||
|
||||
error_msg.includes('Invalid signature') ||
|
||||
error_msg.includes('Authentication required') ||
|
||||
error_msg.includes('HTTP 401')) {
|
||||
this.output_channel.appendLine('Authentication failed, recreating session...');
|
||||
this.auth_data = null;
|
||||
return this.make_request_with_retry(endpoint, data, method, retry_count + 1);
|
||||
}
|
||||
@@ -327,7 +370,7 @@ class IdeBridgeClient {
|
||||
this.show_detailed_error();
|
||||
throw new Error('RSpade project root not found');
|
||||
}
|
||||
const domain_file = path.join(rspade_root, 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
const domain_file = path.join(rspade_root, 'system', 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
this.output_channel.appendLine(`Checking for domain file: ${domain_file}`);
|
||||
if (await exists(domain_file)) {
|
||||
const domain = (await read_file(domain_file, 'utf8')).trim();
|
||||
@@ -338,7 +381,7 @@ class IdeBridgeClient {
|
||||
// domain.txt doesn't exist yet - schedule retry
|
||||
this.schedule_retry();
|
||||
this.show_detailed_error();
|
||||
throw new Error('RSpade: storage/rsx-ide-bridge/domain.txt not found. Please load site in browser or configure server URL.');
|
||||
throw new Error('RSpade: system/storage/rsx-ide-bridge/domain.txt not found. Please load site in browser or configure server URL.');
|
||||
}
|
||||
async negotiate_protocol(url_or_hostname) {
|
||||
// Parse the input to extract hostname
|
||||
@@ -427,7 +470,7 @@ class IdeBridgeClient {
|
||||
if (!rspade_root) {
|
||||
return;
|
||||
}
|
||||
const domain_file = path.join(rspade_root, 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
const domain_file = path.join(rspade_root, 'system', 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
const domain_dir = path.dirname(domain_file);
|
||||
// Watch the directory (file might not exist yet)
|
||||
if (fs.existsSync(domain_dir)) {
|
||||
@@ -469,10 +512,11 @@ class IdeBridgeClient {
|
||||
return undefined;
|
||||
}
|
||||
for (const folder of vscode.workspace.workspaceFolders) {
|
||||
// Try new structure first
|
||||
// Try new structure first - check for system/app/RSpade
|
||||
const system_app_rspade = path.join(folder.uri.fsPath, 'system', 'app', 'RSpade');
|
||||
if (fs.existsSync(system_app_rspade)) {
|
||||
return path.join(folder.uri.fsPath, 'system');
|
||||
// Return project root (not system directory)
|
||||
return folder.uri.fsPath;
|
||||
}
|
||||
// Fall back to legacy structure
|
||||
const app_rspade = path.join(folder.uri.fsPath, 'app', 'RSpade');
|
||||
@@ -507,7 +551,7 @@ class IdeBridgeClient {
|
||||
this.output_channel.appendLine('\nThe extension needs to know your development server URL.');
|
||||
this.output_channel.appendLine('\nPlease do ONE of the following:\n');
|
||||
this.output_channel.appendLine('1. Load your site in a web browser');
|
||||
this.output_channel.appendLine(' This will auto-create: storage/rsx-ide-bridge/domain.txt\n');
|
||||
this.output_channel.appendLine(' This will auto-create: system/storage/rsx-ide-bridge/domain.txt\n');
|
||||
this.output_channel.appendLine('2. Set VS Code setting: rspade.serverUrl');
|
||||
this.output_channel.appendLine(' File → Preferences → Settings → Search "rspade"');
|
||||
this.output_channel.appendLine(' Set to your development URL (e.g., https://myapp.test)\n');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -56,20 +56,21 @@ const LIFECYCLE_DOCS = {
|
||||
on_destroy: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.',
|
||||
};
|
||||
/**
|
||||
* Cache for lineage lookups
|
||||
* Cache for subclass checks
|
||||
*/
|
||||
const lineage_cache = new Map();
|
||||
const subclass_cache = new Map();
|
||||
/**
|
||||
* IDE Bridge client instance (shared across all providers)
|
||||
*/
|
||||
let ide_bridge_client = null;
|
||||
/**
|
||||
* Get JavaScript class lineage from backend via IDE bridge
|
||||
* Check if a JavaScript class extends another class (anywhere in inheritance chain)
|
||||
*/
|
||||
async function get_js_lineage(class_name) {
|
||||
async function is_subclass_of_jqhtml_component(class_name) {
|
||||
const cache_key = `${class_name}:Jqhtml_Component`;
|
||||
// Check cache first
|
||||
if (lineage_cache.has(class_name)) {
|
||||
return lineage_cache.get(class_name);
|
||||
if (subclass_cache.has(cache_key)) {
|
||||
return subclass_cache.get(cache_key);
|
||||
}
|
||||
// Initialize IDE bridge client if needed
|
||||
if (!ide_bridge_client) {
|
||||
@@ -77,15 +78,15 @@ async function get_js_lineage(class_name) {
|
||||
ide_bridge_client = new ide_bridge_client_1.IdeBridgeClient(output_channel);
|
||||
}
|
||||
try {
|
||||
const response = await ide_bridge_client.request('/_ide/service/js_lineage', { class: class_name });
|
||||
const lineage = response.lineage || [];
|
||||
const response = await ide_bridge_client.js_is_subclass_of(class_name, 'Jqhtml_Component');
|
||||
const is_subclass = response.is_subclass || false;
|
||||
// Cache the result
|
||||
lineage_cache.set(class_name, lineage);
|
||||
return lineage;
|
||||
subclass_cache.set(cache_key, is_subclass);
|
||||
return is_subclass;
|
||||
}
|
||||
catch (error) {
|
||||
// Re-throw error to fail loud - no silent fallbacks
|
||||
throw new Error(`Failed to get JS lineage for ${class_name}: ${error.message}`);
|
||||
throw new Error(`Failed to check if ${class_name} extends Jqhtml_Component: ${error.message}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -169,16 +170,14 @@ class JqhtmlLifecycleSemanticTokensProvider {
|
||||
// Check if directly extends Jqhtml_Component
|
||||
const is_jqhtml = directly_extends_jqhtml(text);
|
||||
console.log(`[JQHTML] Directly extends Jqhtml_Component: ${is_jqhtml}`);
|
||||
// If not directly extending, check lineage
|
||||
// If not directly extending, check inheritance chain
|
||||
let extends_jqhtml = is_jqhtml;
|
||||
if (!is_jqhtml && has_extends_clause(text)) {
|
||||
const class_name = extract_class_name(text);
|
||||
console.log(`[JQHTML] Checking lineage for class: ${class_name}`);
|
||||
console.log(`[JQHTML] Checking inheritance for class: ${class_name}`);
|
||||
if (class_name) {
|
||||
const lineage = await get_js_lineage(class_name);
|
||||
console.log(`[JQHTML] Lineage: ${JSON.stringify(lineage)}`);
|
||||
extends_jqhtml = lineage.includes('Jqhtml_Component');
|
||||
console.log(`[JQHTML] Extends Jqhtml_Component via lineage: ${extends_jqhtml}`);
|
||||
extends_jqhtml = await is_subclass_of_jqhtml_component(class_name);
|
||||
console.log(`[JQHTML] Extends Jqhtml_Component via inheritance: ${extends_jqhtml}`);
|
||||
}
|
||||
}
|
||||
// Highlight lifecycle methods (only if extends Jqhtml_Component)
|
||||
@@ -271,8 +270,7 @@ class JqhtmlLifecycleHoverProvider {
|
||||
if (!is_jqhtml) {
|
||||
const class_name = extract_class_name(text);
|
||||
if (class_name) {
|
||||
const lineage = await get_js_lineage(class_name);
|
||||
extends_jqhtml = lineage.includes('Jqhtml_Component');
|
||||
extends_jqhtml = await is_subclass_of_jqhtml_component(class_name);
|
||||
}
|
||||
}
|
||||
if (!extends_jqhtml) {
|
||||
@@ -346,8 +344,7 @@ class JqhtmlLifecycleDiagnosticProvider {
|
||||
if (!is_jqhtml) {
|
||||
const class_name = extract_class_name(text);
|
||||
if (class_name) {
|
||||
const lineage = await get_js_lineage(class_name);
|
||||
extends_jqhtml = lineage.includes('Jqhtml_Component');
|
||||
extends_jqhtml = await is_subclass_of_jqhtml_component(class_name);
|
||||
}
|
||||
}
|
||||
this.document_cache.set(cache_key, extends_jqhtml);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
"name": "rspade-framework",
|
||||
"displayName": "RSpade Framework Support",
|
||||
"description": "VS Code extension for RSpade framework with code folding, formatting, and namespace management",
|
||||
"version": "0.1.182",
|
||||
"version": "0.1.186",
|
||||
"publisher": "rspade",
|
||||
"engines": {
|
||||
"vscode": "^1.74.0"
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { IdeBridgeClient } from './ide_bridge_client';
|
||||
|
||||
/**
|
||||
* Provides automatic file renaming based on RSX naming conventions
|
||||
@@ -17,6 +18,7 @@ export class AutoRenameProvider {
|
||||
private config_enabled: boolean = false;
|
||||
private workspace_root: string = '';
|
||||
private is_checking = false;
|
||||
private ide_bridge_client: IdeBridgeClient | null = null;
|
||||
|
||||
constructor() {
|
||||
this.init();
|
||||
@@ -354,9 +356,25 @@ export class AutoRenameProvider {
|
||||
}
|
||||
|
||||
private async get_suggested_js_filename(file_path: string, class_name: string, content: string): Promise<string> {
|
||||
// Check if this extends Jqhtml_Component
|
||||
const is_jqhtml = content.includes('extends Jqhtml_Component') ||
|
||||
content.match(/extends\s+[A-Za-z0-9_]+\s+extends Jqhtml_Component/);
|
||||
// Check if this extends Jqhtml_Component (directly or via inheritance)
|
||||
let is_jqhtml = content.includes('extends Jqhtml_Component');
|
||||
|
||||
// If not directly extending, check via API
|
||||
if (!is_jqhtml && content.includes('extends ')) {
|
||||
// Initialize IDE bridge client if needed
|
||||
if (!this.ide_bridge_client) {
|
||||
const output_channel = vscode.window.createOutputChannel('RSpade Auto Rename');
|
||||
this.ide_bridge_client = new IdeBridgeClient(output_channel);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.ide_bridge_client.js_is_subclass_of(class_name, 'Jqhtml_Component');
|
||||
is_jqhtml = response.is_subclass || false;
|
||||
} catch (error) {
|
||||
// If API call fails, fall back to direct check only
|
||||
console.log('[AutoRename] JS - Failed to check inheritance:', error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[AutoRename] JS - Is Jqhtml_Component:', is_jqhtml);
|
||||
|
||||
|
||||
@@ -1,3 +1,77 @@
|
||||
/**
|
||||
* RSpade Definition Provider - "Go to Definition" for RSX Classes, Routes, and Components
|
||||
*
|
||||
* RESOLUTION TYPE PRIORITY MATRIX
|
||||
* ================================
|
||||
*
|
||||
* This provider determines what to navigate to when you click "Go to Definition" on various
|
||||
* identifiers across different file types. The resolution logic uses CSV type lists sent to
|
||||
* the server endpoint `/_ide/service/resolve_class?type=X,Y,Z` which tries each type in order.
|
||||
*
|
||||
* FILE TYPE HANDLERS & RESOLUTION RULES:
|
||||
*
|
||||
* 1. ROUTE PATTERNS (all files)
|
||||
* Pattern: Rsx::Route('Controller') or Rsx.Route('Controller', 'method')
|
||||
* Type: 'php_class'
|
||||
* Reason: Routes always point to PHP controllers (server-side)
|
||||
*
|
||||
* 2. HREF PATTERNS (Blade, jqhtml)
|
||||
* Pattern: href="/"
|
||||
* Type: 'php_class'
|
||||
* Reason: Resolves URL to controller, always PHP
|
||||
*
|
||||
* 3. JQHTML EXTENDS ATTRIBUTE (jqhtml only)
|
||||
* Pattern: <Define:My_Component extends="DataGrid_Abstract">
|
||||
* Type: 'jqhtml_class,js_class'
|
||||
* Reason: Component inheritance - try jqhtml component first, then JS class
|
||||
*
|
||||
* 4. JQHTML $xxx ATTRIBUTES (jqhtml only)
|
||||
* Pattern: $data_source=Frontend_Controller.fetch_data
|
||||
* Type: 'js_class,php_class'
|
||||
* Reason: Try JS class first (for components), then PHP (for controllers/models)
|
||||
*
|
||||
* Pattern: $handler=this.on_click
|
||||
* Type: 'jqhtml_class_method'
|
||||
* Special: Resolves to current component's method
|
||||
*
|
||||
* 5. THIS REFERENCES (jqhtml only)
|
||||
* Pattern: <%= this.data.users %>
|
||||
* Type: 'jqhtml_class_method'
|
||||
* Reason: Always current component's method/property
|
||||
*
|
||||
* 6. JAVASCRIPT CLASS REFERENCES (JS, jqhtml)
|
||||
* Pattern: class My_Component extends DataGrid_Abstract
|
||||
* Pattern: User_Controller.fetch_all()
|
||||
* Type: 'js_class,php_class'
|
||||
* Reason: Try JS first (component inheritance), then PHP (controllers/models)
|
||||
* Note: JS stub files (auto-generated from PHP) are client-side only, not in manifest
|
||||
*
|
||||
* 7. PHP CLASS REFERENCES (PHP, Blade)
|
||||
* Pattern: class Contacts_DataGrid extends DataGrid_Abstract
|
||||
* Pattern: use Rsx\Lib\DataGrid_QueryBuilder;
|
||||
* Type: 'php_class'
|
||||
* Reason: In PHP files, class references are always PHP (not JavaScript)
|
||||
*
|
||||
* 8. BUNDLE ALIASES (PHP only)
|
||||
* Pattern: 'include' => ['jqhtml', 'frontend']
|
||||
* Type: 'bundle_alias'
|
||||
* Reason: Resolves to bundle class definition
|
||||
*
|
||||
* 9. VIEW REFERENCES (PHP, Blade)
|
||||
* Pattern: @rsx_extends('frontend.layout')
|
||||
* Pattern: rsx_view('frontend.dashboard')
|
||||
* Type: 'view'
|
||||
* Reason: Resolves to Blade view template files
|
||||
*
|
||||
* METHOD RESOLUTION:
|
||||
* When a pattern includes a method (e.g., Controller.method), the server attempts to find
|
||||
* the specific method in the class. If the method isn't found but the class is, it returns
|
||||
* the class location as a fallback.
|
||||
*
|
||||
* IMPORTANT: The server endpoint supports CSV type lists for priority ordering.
|
||||
* Example: type='php_class,js_class' tries PHP first, then JavaScript.
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
@@ -94,8 +168,21 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle "this.xxx" references in .jqhtml files (highest priority for jqhtml files)
|
||||
// Handle jqhtml-specific patterns
|
||||
if (fileName.endsWith('.jqhtml')) {
|
||||
// Check for extends="ClassName" attribute
|
||||
const extendsResult = await this.handleJqhtmlExtends(document, position);
|
||||
if (extendsResult) {
|
||||
return extendsResult;
|
||||
}
|
||||
|
||||
// Check for $xxx=... attributes (must come before handleThisReference)
|
||||
const attrResult = await this.handleJqhtmlAttribute(document, position);
|
||||
if (attrResult) {
|
||||
return attrResult;
|
||||
}
|
||||
|
||||
// Handle "this.xxx" references in template expressions
|
||||
const thisResult = await this.handleThisReference(document, position);
|
||||
if (thisResult) {
|
||||
return thisResult;
|
||||
@@ -143,6 +230,9 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
* - Rsx::Route('Controller', 'method') (PHP)
|
||||
* - Rsx.Route('Controller') (JavaScript, defaults to 'index')
|
||||
* - Rsx.Route('Controller', 'method') (JavaScript)
|
||||
*
|
||||
* Resolution: Routes always point to PHP controllers (server-side)
|
||||
* Type: 'php_class'
|
||||
*/
|
||||
private async handleRoutePattern(
|
||||
document: vscode.TextDocument,
|
||||
@@ -165,12 +255,12 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
// Always go to the method when clicking anywhere in Route()
|
||||
// This takes precedence over individual class name lookups
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, method, 'class');
|
||||
const result = await this.queryIdeHelper(controller, method, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error) {
|
||||
// If method lookup fails, try just the controller
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'class');
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error2) {
|
||||
console.error('Error querying IDE helper for route:', error);
|
||||
@@ -196,12 +286,12 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
// Single parameter - default to 'index'
|
||||
const method = 'index';
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, method, 'class');
|
||||
const result = await this.queryIdeHelper(controller, method, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error) {
|
||||
// If method lookup fails, try just the controller
|
||||
try {
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'class');
|
||||
const result = await this.queryIdeHelper(controller, undefined, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error2) {
|
||||
console.error('Error querying IDE helper for route:', error);
|
||||
@@ -240,8 +330,8 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
const result = await this.ide_bridge.queryUrl('/');
|
||||
|
||||
if (result && result.found && result.controller && result.method) {
|
||||
// Resolved to controller/method - navigate to it
|
||||
const phpResult = await this.queryIdeHelper(result.controller, result.method, 'class');
|
||||
// Resolved to controller/method - navigate to it (always PHP)
|
||||
const phpResult = await this.queryIdeHelper(result.controller, result.method, 'php_class');
|
||||
return this.createLocationFromResult(phpResult);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -253,6 +343,123 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle jqhtml extends="" attribute
|
||||
* Detects patterns like:
|
||||
* - <Define:My_Component extends="DataGrid_Abstract">
|
||||
*
|
||||
* Resolution: Try jqhtml component first, then JS class
|
||||
* Type: 'jqhtml_class,js_class'
|
||||
*/
|
||||
private async handleJqhtmlExtends(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): Promise<vscode.Definition | undefined> {
|
||||
const line = document.lineAt(position.line).text;
|
||||
|
||||
// Match extends="ClassName" or extends='ClassName'
|
||||
const extendsPattern = /extends\s*=\s*(['"])([A-Z][A-Za-z0-9_]*)\1/g;
|
||||
let match;
|
||||
|
||||
while ((match = extendsPattern.exec(line)) !== null) {
|
||||
const className = match[2];
|
||||
const classStart = match.index + match[0].indexOf(className);
|
||||
const classEnd = classStart + className.length;
|
||||
|
||||
// Check if cursor is on the class name
|
||||
if (position.character >= classStart && position.character < classEnd) {
|
||||
try {
|
||||
// Try jqhtml component first, then JS class
|
||||
const result = await this.queryIdeHelper(className, undefined, 'jqhtml_class,js_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error) {
|
||||
console.error('Error resolving jqhtml extends:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle jqhtml $xxx=... attributes
|
||||
* Detects patterns like:
|
||||
* - $data_source=Frontend_Controller.fetch_data
|
||||
* - $on_click=this.handle_click
|
||||
*
|
||||
* Resolution logic:
|
||||
* - If starts with "this.", resolve to current component's jqhtml class methods
|
||||
* - Otherwise, resolve like JS class references: 'js_class,php_class'
|
||||
*/
|
||||
private async handleJqhtmlAttribute(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): Promise<vscode.Definition | undefined> {
|
||||
const line = document.lineAt(position.line).text;
|
||||
|
||||
// Match $attribute=Value or $attribute=this.method or $attribute=Class.method
|
||||
// Pattern: $word=(this.)?(Word)(.word)?
|
||||
const attrPattern = /\$[a-z_][a-z0-9_]*\s*=\s*(this\.)?([A-Z][A-Za-z0-9_]*)(?:\.([a-z_][a-z0-9_]*))?/gi;
|
||||
let match;
|
||||
|
||||
while ((match = attrPattern.exec(line)) !== null) {
|
||||
const hasThis = !!match[1]; // "this." prefix
|
||||
const className = match[2];
|
||||
const methodName = match[3]; // Optional method after dot
|
||||
|
||||
const classStart = match.index + match[0].indexOf(className);
|
||||
const classEnd = classStart + className.length;
|
||||
|
||||
// Check if cursor is on the class name
|
||||
if (position.character >= classStart && position.character < classEnd) {
|
||||
if (hasThis) {
|
||||
// this.method - resolve to current component's methods
|
||||
// Get the component name from the file
|
||||
let componentName: string | undefined;
|
||||
const fullText = document.getText();
|
||||
const defineMatch = fullText.match(/<Define:([A-Z][A-Za-z0-9_]*)/);
|
||||
|
||||
if (defineMatch) {
|
||||
componentName = defineMatch[1];
|
||||
} else {
|
||||
// If no Define tag, try to get component name from filename
|
||||
const fileName = document.fileName;
|
||||
const baseName = fileName.split('/').pop()?.replace('.jqhtml', '') || '';
|
||||
if (baseName) {
|
||||
// Convert snake_case to PascalCase with underscores
|
||||
componentName = baseName.split('_').map(part =>
|
||||
part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
||||
).join('_');
|
||||
}
|
||||
}
|
||||
|
||||
if (!componentName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
// The className here is actually the method name after "this."
|
||||
// We need to use the component name as the identifier
|
||||
const result = await this.queryIdeHelper(componentName, className.toLowerCase(), 'jqhtml_class_method');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error) {
|
||||
console.error('Error resolving jqhtml this reference:', error);
|
||||
}
|
||||
} else {
|
||||
// Class.method or Class - resolve like JS class references
|
||||
try {
|
||||
const result = await this.queryIdeHelper(className, methodName, 'js_class,php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error) {
|
||||
console.error('Error resolving jqhtml attribute class:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "this.xxx" references in .jqhtml files
|
||||
* Only handles patterns where cursor is on a word after "this."
|
||||
@@ -391,6 +598,19 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
return new vscode.Location(component_def.uri, component_def.position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle JavaScript class references in .js and .jqhtml files
|
||||
* Detects patterns like:
|
||||
* - class My_Component extends DataGrid_Abstract
|
||||
* - User_Controller.fetch_all()
|
||||
* - await Product_Model.fetch(123)
|
||||
*
|
||||
* Resolution: Try JS classes first (for component inheritance), then PHP classes (for controllers/models)
|
||||
* Type: 'js_class,php_class'
|
||||
*
|
||||
* Note: JS stub files (auto-generated from PHP) are client-side only and not in the manifest,
|
||||
* so there's no conflict - the server will correctly return PHP classes when they exist.
|
||||
*/
|
||||
private async handleJavaScriptDefinition(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
@@ -420,8 +640,9 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
}
|
||||
|
||||
// Query the IDE helper endpoint
|
||||
// Try JS classes first (component inheritance), then PHP (controllers/models)
|
||||
try {
|
||||
const result = await this.queryIdeHelper(word, method_name, 'class');
|
||||
const result = await this.queryIdeHelper(word, method_name, 'js_class,php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error) {
|
||||
console.error('Error querying IDE helper:', error);
|
||||
@@ -563,7 +784,9 @@ export class RspadeDefinitionProvider implements vscode.DefinitionProvider {
|
||||
// Check if this looks like an RSX class name
|
||||
if (word.includes('_') && /^[A-Z]/.test(word)) {
|
||||
try {
|
||||
const result = await this.queryIdeHelper(word, undefined, 'class');
|
||||
// When resolving from PHP files, only look for PHP classes
|
||||
// This prevents jumping to JavaScript files when clicking on PHP class references
|
||||
const result = await this.queryIdeHelper(word, undefined, 'php_class');
|
||||
return this.createLocationFromResult(result);
|
||||
} catch (error) {
|
||||
console.error('Error querying IDE helper for class:', error);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Centralized client for communicating with RSpade framework IDE helper endpoints.
|
||||
*
|
||||
* AUTO-DISCOVERY SYSTEM:
|
||||
* 1. Server creates storage/rsx-ide-bridge/domain.txt on first web request
|
||||
* 1. Server creates system/storage/rsx-ide-bridge/domain.txt on first web request
|
||||
* 2. Client reads domain.txt to discover server URL
|
||||
* 3. Falls back to VS Code setting: rspade.serverUrl
|
||||
* 4. Auto-retries with refreshed URL on connection failure
|
||||
@@ -119,6 +119,46 @@ export class IdeBridgeClient {
|
||||
return this.request('/_ide/service/resolve_url', { url }, 'GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a JavaScript class extends another class (anywhere in the inheritance chain)
|
||||
*
|
||||
* @param subclass The potential subclass name
|
||||
* @param superclass The potential superclass name
|
||||
* @returns Promise with { is_subclass: boolean }
|
||||
*/
|
||||
public async js_is_subclass_of(subclass: string, superclass: string): Promise<{ is_subclass: boolean }> {
|
||||
return this.request('/_ide/service/js_is_subclass_of', { subclass, superclass }, 'GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a PHP class extends another class (anywhere in the inheritance chain)
|
||||
*
|
||||
* @param subclass The potential subclass name
|
||||
* @param superclass The potential superclass name
|
||||
* @returns Promise with { is_subclass: boolean }
|
||||
*/
|
||||
public async php_is_subclass_of(subclass: string, superclass: string): Promise<{ is_subclass: boolean }> {
|
||||
return this.request('/_ide/service/php_is_subclass_of', { subclass, superclass }, 'GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger incremental manifest build
|
||||
*
|
||||
* Calls Manifest::init() on the server to update the manifest cache.
|
||||
* Does NOT clear the manifest, just performs incremental update of changed files.
|
||||
*
|
||||
* @returns Promise with { success: boolean }
|
||||
*/
|
||||
public async manifest_build(): Promise<{ success: boolean }> {
|
||||
try {
|
||||
return await this.request('/_ide/service/manifest_build', {}, 'GET');
|
||||
} catch (error: any) {
|
||||
// Log to console but don't throw - errors are silent to user
|
||||
console.warn('[IdeBridge] Manifest build failed:', error.message);
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
|
||||
private async make_request_with_retry(
|
||||
endpoint: string,
|
||||
data: any,
|
||||
@@ -145,9 +185,14 @@ export class IdeBridgeClient {
|
||||
if (retry_count === 0) {
|
||||
const error_msg = error.message || '';
|
||||
|
||||
// Session expired or signature invalid - recreate session
|
||||
if (error_msg.includes('Session not found') || error_msg.includes('Invalid signature')) {
|
||||
this.output_channel.appendLine('Session/signature error, recreating session...');
|
||||
// Authentication failure - recreate session
|
||||
// Handles: "Session not found", "Invalid signature", "Authentication required"
|
||||
// or any HTTP 401 response
|
||||
if (error_msg.includes('Session not found') ||
|
||||
error_msg.includes('Invalid signature') ||
|
||||
error_msg.includes('Authentication required') ||
|
||||
error_msg.includes('HTTP 401')) {
|
||||
this.output_channel.appendLine('Authentication failed, recreating session...');
|
||||
this.auth_data = null;
|
||||
return this.make_request_with_retry(endpoint, data, method, retry_count + 1);
|
||||
}
|
||||
@@ -367,7 +412,7 @@ export class IdeBridgeClient {
|
||||
throw new Error('RSpade project root not found');
|
||||
}
|
||||
|
||||
const domain_file = path.join(rspade_root, 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
const domain_file = path.join(rspade_root, 'system', 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
this.output_channel.appendLine(`Checking for domain file: ${domain_file}`);
|
||||
|
||||
if (await exists(domain_file)) {
|
||||
@@ -380,7 +425,7 @@ export class IdeBridgeClient {
|
||||
// domain.txt doesn't exist yet - schedule retry
|
||||
this.schedule_retry();
|
||||
this.show_detailed_error();
|
||||
throw new Error('RSpade: storage/rsx-ide-bridge/domain.txt not found. Please load site in browser or configure server URL.');
|
||||
throw new Error('RSpade: system/storage/rsx-ide-bridge/domain.txt not found. Please load site in browser or configure server URL.');
|
||||
}
|
||||
|
||||
private async negotiate_protocol(url_or_hostname: string): Promise<string> {
|
||||
@@ -483,7 +528,7 @@ export class IdeBridgeClient {
|
||||
return;
|
||||
}
|
||||
|
||||
const domain_file = path.join(rspade_root, 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
const domain_file = path.join(rspade_root, 'system', 'storage', 'rsx-ide-bridge', 'domain.txt');
|
||||
const domain_dir = path.dirname(domain_file);
|
||||
|
||||
// Watch the directory (file might not exist yet)
|
||||
@@ -531,10 +576,11 @@ export class IdeBridgeClient {
|
||||
}
|
||||
|
||||
for (const folder of vscode.workspace.workspaceFolders) {
|
||||
// Try new structure first
|
||||
// Try new structure first - check for system/app/RSpade
|
||||
const system_app_rspade = path.join(folder.uri.fsPath, 'system', 'app', 'RSpade');
|
||||
if (fs.existsSync(system_app_rspade)) {
|
||||
return path.join(folder.uri.fsPath, 'system');
|
||||
// Return project root (not system directory)
|
||||
return folder.uri.fsPath;
|
||||
}
|
||||
|
||||
// Fall back to legacy structure
|
||||
@@ -576,7 +622,7 @@ export class IdeBridgeClient {
|
||||
this.output_channel.appendLine('\nThe extension needs to know your development server URL.');
|
||||
this.output_channel.appendLine('\nPlease do ONE of the following:\n');
|
||||
this.output_channel.appendLine('1. Load your site in a web browser');
|
||||
this.output_channel.appendLine(' This will auto-create: storage/rsx-ide-bridge/domain.txt\n');
|
||||
this.output_channel.appendLine(' This will auto-create: system/storage/rsx-ide-bridge/domain.txt\n');
|
||||
this.output_channel.appendLine('2. Set VS Code setting: rspade.serverUrl');
|
||||
this.output_channel.appendLine(' File → Preferences → Settings → Search "rspade"');
|
||||
this.output_channel.appendLine(' Set to your development URL (e.g., https://myapp.test)\n');
|
||||
|
||||
@@ -34,9 +34,9 @@ const LIFECYCLE_DOCS: { [key: string]: string } = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Cache for lineage lookups
|
||||
* Cache for subclass checks
|
||||
*/
|
||||
const lineage_cache = new Map<string, string[]>();
|
||||
const subclass_cache = new Map<string, boolean>();
|
||||
|
||||
/**
|
||||
* IDE Bridge client instance (shared across all providers)
|
||||
@@ -44,12 +44,14 @@ const lineage_cache = new Map<string, string[]>();
|
||||
let ide_bridge_client: IdeBridgeClient | null = null;
|
||||
|
||||
/**
|
||||
* Get JavaScript class lineage from backend via IDE bridge
|
||||
* Check if a JavaScript class extends another class (anywhere in inheritance chain)
|
||||
*/
|
||||
async function get_js_lineage(class_name: string): Promise<string[]> {
|
||||
async function is_subclass_of_jqhtml_component(class_name: string): Promise<boolean> {
|
||||
const cache_key = `${class_name}:Jqhtml_Component`;
|
||||
|
||||
// Check cache first
|
||||
if (lineage_cache.has(class_name)) {
|
||||
return lineage_cache.get(class_name)!;
|
||||
if (subclass_cache.has(cache_key)) {
|
||||
return subclass_cache.get(cache_key)!;
|
||||
}
|
||||
|
||||
// Initialize IDE bridge client if needed
|
||||
@@ -59,16 +61,16 @@ async function get_js_lineage(class_name: string): Promise<string[]> {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await ide_bridge_client.request('/_ide/service/js_lineage', { class: class_name });
|
||||
const lineage = response.lineage || [];
|
||||
const response = await ide_bridge_client.js_is_subclass_of(class_name, 'Jqhtml_Component');
|
||||
const is_subclass = response.is_subclass || false;
|
||||
|
||||
// Cache the result
|
||||
lineage_cache.set(class_name, lineage);
|
||||
subclass_cache.set(cache_key, is_subclass);
|
||||
|
||||
return lineage;
|
||||
return is_subclass;
|
||||
} catch (error: any) {
|
||||
// Re-throw error to fail loud - no silent fallbacks
|
||||
throw new Error(`Failed to get JS lineage for ${class_name}: ${error.message}`);
|
||||
throw new Error(`Failed to check if ${class_name} extends Jqhtml_Component: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,17 +166,15 @@ export class JqhtmlLifecycleSemanticTokensProvider implements vscode.DocumentSem
|
||||
const is_jqhtml = directly_extends_jqhtml(text);
|
||||
console.log(`[JQHTML] Directly extends Jqhtml_Component: ${is_jqhtml}`);
|
||||
|
||||
// If not directly extending, check lineage
|
||||
// If not directly extending, check inheritance chain
|
||||
let extends_jqhtml = is_jqhtml;
|
||||
|
||||
if (!is_jqhtml && has_extends_clause(text)) {
|
||||
const class_name = extract_class_name(text);
|
||||
console.log(`[JQHTML] Checking lineage for class: ${class_name}`);
|
||||
console.log(`[JQHTML] Checking inheritance for class: ${class_name}`);
|
||||
if (class_name) {
|
||||
const lineage = await get_js_lineage(class_name);
|
||||
console.log(`[JQHTML] Lineage: ${JSON.stringify(lineage)}`);
|
||||
extends_jqhtml = lineage.includes('Jqhtml_Component');
|
||||
console.log(`[JQHTML] Extends Jqhtml_Component via lineage: ${extends_jqhtml}`);
|
||||
extends_jqhtml = await is_subclass_of_jqhtml_component(class_name);
|
||||
console.log(`[JQHTML] Extends Jqhtml_Component via inheritance: ${extends_jqhtml}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,8 +286,7 @@ export class JqhtmlLifecycleHoverProvider implements vscode.HoverProvider {
|
||||
if (!is_jqhtml) {
|
||||
const class_name = extract_class_name(text);
|
||||
if (class_name) {
|
||||
const lineage = await get_js_lineage(class_name);
|
||||
extends_jqhtml = lineage.includes('Jqhtml_Component');
|
||||
extends_jqhtml = await is_subclass_of_jqhtml_component(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,8 +384,7 @@ export class JqhtmlLifecycleDiagnosticProvider {
|
||||
if (!is_jqhtml) {
|
||||
const class_name = extract_class_name(text);
|
||||
if (class_name) {
|
||||
const lineage = await get_js_lineage(class_name);
|
||||
extends_jqhtml = lineage.includes('Jqhtml_Component');
|
||||
extends_jqhtml = await is_subclass_of_jqhtml_component(class_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
node_modules/.package-lock.json
generated
vendored
34
node_modules/.package-lock.json
generated
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "html",
|
||||
"name": "system",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@@ -2227,9 +2227,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/core": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.2.137.tgz",
|
||||
"integrity": "sha512-GViirzsF3VfYe7boTyeP35VxhrWSkerbghnzrPwdag+9LrDKqjc76tDCP8XtO7ddgVlm/VDZpvUQQUXEd57eDA==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.2.142.tgz",
|
||||
"integrity": "sha512-8X1p+z82Y1sRm6pp7tfuakfN43xx0BgIEXf2Wz/C6mTSn8pygmOREE8Leodwdy9EQtZXif8N8Qmtz50s7X4wjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
@@ -2253,9 +2253,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/parser": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.2.137.tgz",
|
||||
"integrity": "sha512-DC1GlzZvMhlqxJDNjJ184r+h21W+HhS6GRcG2W/Eo75LnmehakMG9yO4WHeZ9809isl/7+fEwZbk2jM+FD3KVA==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.2.142.tgz",
|
||||
"integrity": "sha512-ATn7qrZPWNi/G2WkDUchicGyCTqDLKFYjfE69ckie5S+zRS8zTAUJSS60N+LzUdwgM5N9v8V7C//6P85fggYuw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jest": "^29.5.11",
|
||||
@@ -2273,9 +2273,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/router": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/router/-/router-2.2.137.tgz",
|
||||
"integrity": "sha512-ybiQ6SRZxbYDPwrOML99T9gWJqNM2w43QnptJwWMg+E2zvAr48KpVJhcNJlsOlqGykME5PJOvxioMdMlfaEG5A==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/router/-/router-2.2.142.tgz",
|
||||
"integrity": "sha512-x9gCid0jvkMWFKSbFEcFl6iygFGiDtYbZBai5LOBipUMXUlQw13rJv+ZBe1W+SM2PorUTuJXoTY6O+OxDJS/1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
@@ -2293,21 +2293,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/vscode-extension": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.2.137.tgz",
|
||||
"integrity": "sha512-9xM9/JqXKestgeivCCWfr49RD0D279ZG/K/PxU2u4kc+mE1kEwpkmVnN4QGGw4rzXUjPFdIoG3ogaMY2gRjFew==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.2.142.tgz",
|
||||
"integrity": "sha512-3A8dOjpK01SgxMC3rTjgHNgRvQLKHdBnGFlWCu2Qk1f70DqTHGGwPUbWHNEQcpQw3LhjINYF8WQ8yu/Dkl9Qnw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.74.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/webpack-loader": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/webpack-loader/-/webpack-loader-2.2.137.tgz",
|
||||
"integrity": "sha512-kSWsTnGa5USX8HKFYdAhfdAD5l/UiIwBJXmUZeLYaLSZvCePBEMfrk9rKbPtfkpNrbTnHhipF6DZmSOElmoPAg==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/webpack-loader/-/webpack-loader-2.2.142.tgz",
|
||||
"integrity": "sha512-SUTREV2M1bUJb5s9h4b4q6cTDAl6EBtbPk7J2hMFpy7XwKpv68DVI2lCAOVOwjM1xQXDiTGOM+0gwWqwzEqE+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jqhtml/parser": "2.2.137",
|
||||
"@jqhtml/parser": "2.2.142",
|
||||
"@types/loader-utils": "^2.0.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/webpack": "^5.28.5",
|
||||
|
||||
2
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
2
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
@@ -2728,7 +2728,7 @@ function init(jQuery) {
|
||||
}
|
||||
}
|
||||
// Version - will be replaced during build with actual version from package.json
|
||||
const version = '2.2.137';
|
||||
const version = '2.2.142';
|
||||
// Default export with all functionality
|
||||
const jqhtml = {
|
||||
// Core
|
||||
|
||||
2
node_modules/@jqhtml/core/dist/index.js
generated
vendored
2
node_modules/@jqhtml/core/dist/index.js
generated
vendored
@@ -2724,7 +2724,7 @@ function init(jQuery) {
|
||||
}
|
||||
}
|
||||
// Version - will be replaced during build with actual version from package.json
|
||||
const version = '2.2.137';
|
||||
const version = '2.2.142';
|
||||
// Default export with all functionality
|
||||
const jqhtml = {
|
||||
// Core
|
||||
|
||||
4
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
4
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* JQHTML Core v2.2.137
|
||||
* JQHTML Core v2.2.142
|
||||
* (c) 2025 JQHTML Team
|
||||
* Released under the MIT License
|
||||
*/
|
||||
@@ -2729,7 +2729,7 @@ function init(jQuery) {
|
||||
}
|
||||
}
|
||||
// Version - will be replaced during build with actual version from package.json
|
||||
const version = '2.2.137';
|
||||
const version = '2.2.142';
|
||||
// Default export with all functionality
|
||||
const jqhtml = {
|
||||
// Core
|
||||
|
||||
2
node_modules/@jqhtml/core/package.json
generated
vendored
2
node_modules/@jqhtml/core/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/core",
|
||||
"version": "2.2.137",
|
||||
"version": "2.2.142",
|
||||
"description": "Core runtime library for JQHTML",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
@@ -1201,7 +1201,7 @@ export class CodeGenerator {
|
||||
for (const [name, component] of this.components) {
|
||||
code += `// Component: ${name}\n`;
|
||||
code += `jqhtml_components.set('${name}', {\n`;
|
||||
code += ` _jqhtml_version: '2.2.137',\n`; // Version will be replaced during build
|
||||
code += ` _jqhtml_version: '2.2.142',\n`; // Version will be replaced during build
|
||||
code += ` name: '${name}',\n`;
|
||||
code += ` tag: '${component.tagName}',\n`;
|
||||
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
||||
|
||||
2
node_modules/@jqhtml/parser/package.json
generated
vendored
2
node_modules/@jqhtml/parser/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/parser",
|
||||
"version": "2.2.137",
|
||||
"version": "2.2.142",
|
||||
"description": "JQHTML template parser - converts templates to JavaScript",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
2
node_modules/@jqhtml/router/dist/jqhtml-router.esm.js
generated
vendored
2
node_modules/@jqhtml/router/dist/jqhtml-router.esm.js
generated
vendored
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* JQHTML Router v2.2.137
|
||||
* JQHTML Router v2.2.142
|
||||
* (c) 2025 JQHTML Team
|
||||
* Released under the MIT License
|
||||
*/
|
||||
|
||||
2
node_modules/@jqhtml/router/package.json
generated
vendored
2
node_modules/@jqhtml/router/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/router",
|
||||
"version": "2.2.137",
|
||||
"version": "2.2.142",
|
||||
"description": "Client-side routing for JQHTML applications",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
@@ -1 +1 @@
|
||||
2.2.137
|
||||
2.2.142
|
||||
|
||||
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.137.vsix
generated
vendored
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.137.vsix
generated
vendored
Binary file not shown.
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.142.vsix
generated
vendored
Executable file
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.2.142.vsix
generated
vendored
Executable file
Binary file not shown.
10
node_modules/@jqhtml/vscode-extension/out/componentIndex.js
generated
vendored
10
node_modules/@jqhtml/vscode-extension/out/componentIndex.js
generated
vendored
@@ -44,6 +44,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.JqhtmlComponentIndex = void 0;
|
||||
const vscode = __importStar(require("vscode"));
|
||||
const path = __importStar(require("path"));
|
||||
/**
|
||||
* JQHTML Component Indexer
|
||||
*
|
||||
@@ -97,6 +98,11 @@ class JqhtmlComponentIndex {
|
||||
const files = yield vscode.workspace.findFiles(new vscode.RelativePattern(folder, '**/*.jqhtml'), new vscode.RelativePattern(folder, '**/node_modules/**'));
|
||||
allFiles.push(...files);
|
||||
}
|
||||
// Debug: Log all discovered files
|
||||
console.log(`JQHTML: Found ${allFiles.length} .jqhtml files to index:`);
|
||||
allFiles.forEach(uri => {
|
||||
console.log(` - ${uri.fsPath}`);
|
||||
});
|
||||
// Index each file
|
||||
const promises = allFiles.map(uri => this.indexFile(uri));
|
||||
yield Promise.all(promises);
|
||||
@@ -155,8 +161,8 @@ class JqhtmlComponentIndex {
|
||||
position: new vscode.Position(lineNum, charPos),
|
||||
line: line.trim()
|
||||
});
|
||||
// Debug: Log each component as it's indexed (commented out - too verbose)
|
||||
// console.log(`JQHTML Index: Indexed "${componentName}" from ${path.basename(uri.fsPath)}:${lineNum + 1}`);
|
||||
// Debug: Log each component as it's indexed
|
||||
console.log(`JQHTML Index: Indexed "${componentName}" from ${path.basename(uri.fsPath)}:${lineNum + 1}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
node_modules/@jqhtml/vscode-extension/out/componentIndex.js.map
generated
vendored
2
node_modules/@jqhtml/vscode-extension/out/componentIndex.js.map
generated
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"componentIndex.js","sourceRoot":"","sources":["../src/componentIndex.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"componentIndex.js","sourceRoot":"","sources":["../src/componentIndex.ts"],"names":[],"mappings":"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"}
|
||||
183
node_modules/@jqhtml/vscode-extension/out/definitionProvider.js
generated
vendored
183
node_modules/@jqhtml/vscode-extension/out/definitionProvider.js
generated
vendored
@@ -69,6 +69,19 @@ class JqhtmlDefinitionProvider {
|
||||
console.log(`JQHTML: In $ attribute context:`, dollarAttrResult);
|
||||
return yield this.handleDollarAttributeDefinition(document, position, dollarAttrResult);
|
||||
}
|
||||
// IMPORTANT: Check for slot syntax BEFORE extracting word
|
||||
// This prevents slot names from being treated as component names
|
||||
// Check if we're in a slot tag by looking for <# or </# before cursor
|
||||
const beforeCursor = line.substring(0, position.character);
|
||||
if (beforeCursor.match(/<\/?#\s*[A-Z][A-Za-z0-9_]*$/)) {
|
||||
// We're in a slot tag - extract the full slot name from the line
|
||||
const slotNameMatch = line.match(/<\/?#\s*([A-Z][A-Za-z0-9_]*)/);
|
||||
if (slotNameMatch) {
|
||||
const slotName = slotNameMatch[1];
|
||||
console.log(`JQHTML: Detected slot tag syntax for slot: ${slotName}`);
|
||||
return yield this.handleSlotDefinition(document, position, slotName);
|
||||
}
|
||||
}
|
||||
// Get the word at the cursor position
|
||||
const wordRange = document.getWordRangeAtPosition(position, /[A-Z][A-Za-z0-9_]*/);
|
||||
if (!wordRange) {
|
||||
@@ -119,11 +132,6 @@ class JqhtmlDefinitionProvider {
|
||||
if (beforeWord.match(/<\/\s*$/) || beforeWord.match(/<\/Define:\s*$/)) {
|
||||
isInTagContext = true;
|
||||
}
|
||||
// Check for slot syntax: <#slotname> or </#slotname>
|
||||
if (beforeWord.match(/<#\s*$/) || beforeWord.match(/<\/#\s*$/)) {
|
||||
// This is a slot, not a component
|
||||
return undefined;
|
||||
}
|
||||
if (!isInTagContext) {
|
||||
// Also check if cursor is inside the tag name (not in attributes)
|
||||
const afterWord = line.substring(wordRange.end.character);
|
||||
@@ -483,6 +491,171 @@ class JqhtmlDefinitionProvider {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Handle goto definition for slot tags (<#SlotName>)
|
||||
*
|
||||
* IMPLEMENTATION SCOPE (Narrow, for now):
|
||||
* - Handles direct extends="ComponentName" on <Define:> tags
|
||||
* - Handles direct <ComponentName> invocation tags
|
||||
* - Does NOT traverse full inheritance chain (TODO: add later)
|
||||
* - Just looks for direct parent component
|
||||
*
|
||||
* LOGIC:
|
||||
* 1. Extract slot name from cursor position
|
||||
* 2. Find parent component:
|
||||
* - If inside <Define extends="Parent">, use Parent
|
||||
* - If inside <Parent> invocation, use Parent
|
||||
* 3. Find Parent.jqhtml file
|
||||
* 4. Search for <%= content('SlotName') %>
|
||||
* 5. Navigate to that line
|
||||
*/
|
||||
handleSlotDefinition(document, position, slotName) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
console.log(`JQHTML: Handling slot definition for: ${slotName}`);
|
||||
// Find the parent component that defines this slot
|
||||
const parentComponentName = this.findParentComponentForSlot(document, position);
|
||||
if (!parentComponentName) {
|
||||
console.log(`JQHTML: Could not determine parent component for slot`);
|
||||
return undefined;
|
||||
}
|
||||
console.log(`JQHTML: Parent component for slot: ${parentComponentName}`);
|
||||
// Debug: Show what's in the index
|
||||
const allComponents = this.componentIndex.getAllComponentNames();
|
||||
console.log(`JQHTML: Index currently contains ${allComponents.length} components:`, allComponents.join(', '));
|
||||
// Find the parent component definition file
|
||||
const parentComponent = this.componentIndex.findComponent(parentComponentName);
|
||||
if (!parentComponent) {
|
||||
console.log(`JQHTML: Parent component '${parentComponentName}' not found in index`);
|
||||
return undefined;
|
||||
}
|
||||
console.log(`JQHTML: Found parent component file: ${parentComponent.uri.fsPath}`);
|
||||
// Search for content('SlotName') in the parent component file
|
||||
const slotUsageLocation = yield this.findSlotUsageInTemplate(parentComponent.uri, slotName);
|
||||
if (!slotUsageLocation) {
|
||||
console.log(`JQHTML: Slot usage content('${slotName}') not found in ${parentComponent.uri.fsPath}`);
|
||||
return undefined;
|
||||
}
|
||||
console.log(`JQHTML: Found slot usage at line ${slotUsageLocation.range.start.line + 1}`);
|
||||
return slotUsageLocation;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Find the parent component that should define this slot
|
||||
*
|
||||
* Looks for either:
|
||||
* 1. <Define:ChildComponent extends="ParentComponent"> - check if slots are top-level
|
||||
* 2. <ParentComponent> - find enclosing component invocation tag
|
||||
*/
|
||||
findParentComponentForSlot(document, position) {
|
||||
const currentLine = position.line;
|
||||
// Strategy 1: Look for <Define extends="ParentComponent"> where slots are at top level
|
||||
// Scan upward to find the Define tag
|
||||
let defineTagStartLine = -1;
|
||||
for (let i = currentLine; i >= 0; i--) {
|
||||
const lineText = document.lineAt(i).text;
|
||||
// Check if we found a <Define:ComponentName
|
||||
if (lineText.match(/<Define:([A-Z][A-Za-z0-9_]*)/)) {
|
||||
defineTagStartLine = i;
|
||||
console.log(`JQHTML: Found <Define: tag at line ${i + 1}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we found a Define tag, look for extends attribute in the tag (may be multi-line)
|
||||
if (defineTagStartLine >= 0) {
|
||||
// Collect all lines from Define tag start until we find the closing >
|
||||
let tagContent = '';
|
||||
for (let i = defineTagStartLine; i < document.lineCount; i++) {
|
||||
const lineText = document.lineAt(i).text;
|
||||
tagContent += lineText + ' ';
|
||||
// Stop when we find the closing > of the opening tag
|
||||
if (lineText.includes('>')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Now check if this multi-line tag has extends attribute
|
||||
const extendsMatch = tagContent.match(/\bextends\s*=\s*["']([A-Z][A-Za-z0-9_]*)["']/);
|
||||
if (extendsMatch) {
|
||||
const parentComponentName = extendsMatch[1];
|
||||
console.log(`JQHTML: Found extends="${parentComponentName}" in Define tag`);
|
||||
// TODO: Verify that the slot is at top level (not nested inside other tags)
|
||||
// For now, we assume if we found a Define with extends, that's the parent
|
||||
return parentComponentName;
|
||||
}
|
||||
else {
|
||||
console.log(`JQHTML: Define tag found but no extends attribute`);
|
||||
}
|
||||
}
|
||||
// Strategy 2: Look for enclosing <ParentComponent> invocation tag
|
||||
// Scan upward to find opening tag
|
||||
let tagStack = [];
|
||||
for (let i = currentLine; i >= 0; i--) {
|
||||
const lineText = document.lineAt(i).text;
|
||||
// Find all component tags on this line (both opening and closing)
|
||||
// Component tags: <ComponentName> or </ComponentName>
|
||||
const tagRegex = /<\/?([A-Z][A-Za-z0-9_]*)[^>]*>/g;
|
||||
let match;
|
||||
// Collect all tags on this line
|
||||
const tagsOnLine = [];
|
||||
while ((match = tagRegex.exec(lineText)) !== null) {
|
||||
const fullMatch = match[0];
|
||||
const componentName = match[1];
|
||||
const isClosing = fullMatch.startsWith('</');
|
||||
tagsOnLine.push({ tag: componentName, isClosing });
|
||||
}
|
||||
// Process tags in reverse order (right to left on the line)
|
||||
for (let j = tagsOnLine.length - 1; j >= 0; j--) {
|
||||
const { tag, isClosing } = tagsOnLine[j];
|
||||
if (isClosing) {
|
||||
// Closing tag - add to stack
|
||||
tagStack.push(tag);
|
||||
}
|
||||
else {
|
||||
// Opening tag
|
||||
if (tagStack.length > 0 && tagStack[tagStack.length - 1] === tag) {
|
||||
// This opening tag matches the last closing tag on stack - they cancel out
|
||||
tagStack.pop();
|
||||
}
|
||||
else {
|
||||
// This is an unclosed opening tag - this is our parent!
|
||||
console.log(`JQHTML: Found enclosing component invocation: <${tag}>`);
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`JQHTML: No parent component found for slot`);
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Search for <%= content('SlotName') %> in a template file
|
||||
*/
|
||||
findSlotUsageInTemplate(templateUri, slotName) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const templateDoc = yield vscode.workspace.openTextDocument(templateUri);
|
||||
const templateText = templateDoc.getText();
|
||||
// Search for content('SlotName') or content("SlotName")
|
||||
// Also handle optional whitespace
|
||||
const contentRegex = new RegExp(`<%=\\s*content\\s*\\(\\s*['"]${slotName}['"]\\s*\\)`, 'g');
|
||||
const match = contentRegex.exec(templateText);
|
||||
if (match) {
|
||||
const matchPosition = templateDoc.positionAt(match.index);
|
||||
console.log(`JQHTML: Found content('${slotName}') at line ${matchPosition.line + 1}`);
|
||||
// Return location pointing to the slot name within content('SlotName')
|
||||
const slotNameStartIndex = match.index + match[0].indexOf(slotName);
|
||||
const slotNamePosition = templateDoc.positionAt(slotNameStartIndex);
|
||||
const slotNameRange = new vscode.Range(slotNamePosition, new vscode.Position(slotNamePosition.line, slotNamePosition.character + slotName.length));
|
||||
return new vscode.Location(templateUri, slotNameRange);
|
||||
}
|
||||
console.log(`JQHTML: No content('${slotName}') found in template`);
|
||||
return undefined;
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`JQHTML: Error reading template file:`, error);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.JqhtmlDefinitionProvider = JqhtmlDefinitionProvider;
|
||||
/**
|
||||
|
||||
2
node_modules/@jqhtml/vscode-extension/out/definitionProvider.js.map
generated
vendored
2
node_modules/@jqhtml/vscode-extension/out/definitionProvider.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
@@ -2,7 +2,7 @@
|
||||
"name": "@jqhtml/vscode-extension",
|
||||
"displayName": "JQHTML",
|
||||
"description": "Syntax highlighting and language support for JQHTML template files",
|
||||
"version": "2.2.137",
|
||||
"version": "2.2.142",
|
||||
"publisher": "jqhtml",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
|
||||
4
node_modules/@jqhtml/webpack-loader/package.json
generated
vendored
4
node_modules/@jqhtml/webpack-loader/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jqhtml/webpack-loader",
|
||||
"version": "2.2.137",
|
||||
"version": "2.2.142",
|
||||
"description": "Webpack loader for JQHTML templates",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -30,7 +30,7 @@
|
||||
"template"
|
||||
],
|
||||
"dependencies": {
|
||||
"@jqhtml/parser": "2.2.137",
|
||||
"@jqhtml/parser": "2.2.142",
|
||||
"@types/loader-utils": "^2.0.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/webpack": "^5.28.5",
|
||||
|
||||
34
package-lock.json
generated
34
package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "html",
|
||||
"name": "system",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@@ -2658,9 +2658,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/core": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.2.137.tgz",
|
||||
"integrity": "sha512-GViirzsF3VfYe7boTyeP35VxhrWSkerbghnzrPwdag+9LrDKqjc76tDCP8XtO7ddgVlm/VDZpvUQQUXEd57eDA==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.2.142.tgz",
|
||||
"integrity": "sha512-8X1p+z82Y1sRm6pp7tfuakfN43xx0BgIEXf2Wz/C6mTSn8pygmOREE8Leodwdy9EQtZXif8N8Qmtz50s7X4wjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
@@ -2684,9 +2684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/parser": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.2.137.tgz",
|
||||
"integrity": "sha512-DC1GlzZvMhlqxJDNjJ184r+h21W+HhS6GRcG2W/Eo75LnmehakMG9yO4WHeZ9809isl/7+fEwZbk2jM+FD3KVA==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.2.142.tgz",
|
||||
"integrity": "sha512-ATn7qrZPWNi/G2WkDUchicGyCTqDLKFYjfE69ckie5S+zRS8zTAUJSS60N+LzUdwgM5N9v8V7C//6P85fggYuw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jest": "^29.5.11",
|
||||
@@ -2704,9 +2704,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/router": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/router/-/router-2.2.137.tgz",
|
||||
"integrity": "sha512-ybiQ6SRZxbYDPwrOML99T9gWJqNM2w43QnptJwWMg+E2zvAr48KpVJhcNJlsOlqGykME5PJOvxioMdMlfaEG5A==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/router/-/router-2.2.142.tgz",
|
||||
"integrity": "sha512-x9gCid0jvkMWFKSbFEcFl6iygFGiDtYbZBai5LOBipUMXUlQw13rJv+ZBe1W+SM2PorUTuJXoTY6O+OxDJS/1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
@@ -2724,21 +2724,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/vscode-extension": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.2.137.tgz",
|
||||
"integrity": "sha512-9xM9/JqXKestgeivCCWfr49RD0D279ZG/K/PxU2u4kc+mE1kEwpkmVnN4QGGw4rzXUjPFdIoG3ogaMY2gRjFew==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.2.142.tgz",
|
||||
"integrity": "sha512-3A8dOjpK01SgxMC3rTjgHNgRvQLKHdBnGFlWCu2Qk1f70DqTHGGwPUbWHNEQcpQw3LhjINYF8WQ8yu/Dkl9Qnw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.74.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jqhtml/webpack-loader": {
|
||||
"version": "2.2.137",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/webpack-loader/-/webpack-loader-2.2.137.tgz",
|
||||
"integrity": "sha512-kSWsTnGa5USX8HKFYdAhfdAD5l/UiIwBJXmUZeLYaLSZvCePBEMfrk9rKbPtfkpNrbTnHhipF6DZmSOElmoPAg==",
|
||||
"version": "2.2.142",
|
||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/webpack-loader/-/webpack-loader-2.2.142.tgz",
|
||||
"integrity": "sha512-SUTREV2M1bUJb5s9h4b4q6cTDAl6EBtbPk7J2hMFpy7XwKpv68DVI2lCAOVOwjM1xQXDiTGOM+0gwWqwzEqE+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jqhtml/parser": "2.2.137",
|
||||
"@jqhtml/parser": "2.2.142",
|
||||
"@types/loader-utils": "^2.0.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/webpack": "^5.28.5",
|
||||
|
||||
@@ -17,12 +17,47 @@ define('LARAVEL_START', microtime(true));
|
||||
|
||||
$request_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
|
||||
// Handle new IDE service endpoints (not the legacy _idehelper which uses Laravel)
|
||||
// Handle IDE service endpoints
|
||||
if (str_starts_with($request_path, '/_ide/service')) {
|
||||
// Handle IDE services with authentication
|
||||
$service_handler = __DIR__ . '/../app/RSpade/Ide/Services/handler.php';
|
||||
if (file_exists($service_handler)) {
|
||||
require_once $service_handler;
|
||||
// SECURITY-CRITICAL: Authenticate FIRST before any service logic
|
||||
// This checks session auth OR localhost bypass before proceeding
|
||||
require_once __DIR__ . '/../app/RSpade/Ide/Services/auth.php';
|
||||
|
||||
// If we reach here, authentication passed (auth.php exits on failure)
|
||||
|
||||
// SECURITY: Explicit whitelist only - handlers must be explicitly defined here.
|
||||
// User input (service name) determines WHICH handler, but cannot inject arbitrary paths.
|
||||
// TODO: Improve the design of this subsystem invocation later.
|
||||
|
||||
// Extract service name
|
||||
$service_name = str_replace('/_ide/service', '', $request_path);
|
||||
$service_name = trim($service_name, '/');
|
||||
|
||||
// Whitelist of allowed handlers
|
||||
$allowed_handlers = [
|
||||
'format' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
'definition' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
'complete' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
'exec' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
'command' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
'resolve_class' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
'git' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
'git/diff' => __DIR__ . '/../app/RSpade/Ide/Services/handler.php',
|
||||
// All other services use the Laravel handler
|
||||
'default' => __DIR__ . '/../app/RSpade/Ide/Services/laravel_handler.php',
|
||||
];
|
||||
|
||||
// Determine which handler to use
|
||||
if (isset($allowed_handlers[$service_name])) {
|
||||
$handler_path = $allowed_handlers[$service_name];
|
||||
} else {
|
||||
// Services not explicitly listed use the Laravel handler
|
||||
$handler_path = $allowed_handlers['default'];
|
||||
}
|
||||
|
||||
// Execute the whitelisted handler
|
||||
if (file_exists($handler_path)) {
|
||||
require_once $handler_path;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user