Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
238 lines
8.8 KiB
PHP
Executable File
238 lines
8.8 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\RSpade\CodeQuality\Rules\Common;
|
|
|
|
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
|
|
|
|
/**
|
|
* RouteExistsRule - Validates that Rsx::Route() calls reference existing routes
|
|
*
|
|
* This rule checks both PHP and JavaScript files for Route() calls with literal
|
|
* string parameters and validates that the referenced controller and method
|
|
* combination actually exists as a route in the manifest.
|
|
*
|
|
* Example violations:
|
|
* - Rsx::Route('NonExistent_Controller')
|
|
* - Route('Some_Controller', 'missing_method')
|
|
*
|
|
* The rule only checks when both parameters are string literals, not variables.
|
|
*/
|
|
class RouteExists_CodeQualityRule extends CodeQualityRule_Abstract
|
|
{
|
|
|
|
/**
|
|
* Get the unique rule identifier
|
|
*/
|
|
public function get_id(): string
|
|
{
|
|
return 'ROUTE-EXISTS-01';
|
|
}
|
|
|
|
/**
|
|
* Get human-readable rule name
|
|
*/
|
|
public function get_name(): string
|
|
{
|
|
return 'Route Target Exists Validation';
|
|
}
|
|
|
|
/**
|
|
* Get rule description
|
|
*/
|
|
public function get_description(): string
|
|
{
|
|
return 'Validates that Rsx::Route() calls reference controller methods that actually exist as routes';
|
|
}
|
|
|
|
/**
|
|
* Get file patterns this rule applies to
|
|
*/
|
|
public function get_file_patterns(): array
|
|
{
|
|
return ['*.php', '*.js', '*.blade.php'];
|
|
}
|
|
|
|
/**
|
|
* Whether this rule is called during manifest scan
|
|
*
|
|
* IMPORTANT: This method should ALWAYS return false unless explicitly requested
|
|
* by the framework developer. Manifest-time checks are reserved for critical
|
|
* framework convention violations that need immediate developer attention.
|
|
*
|
|
* Rules executed during manifest scan will run on every file change in development,
|
|
* potentially impacting performance. Only enable this for rules that:
|
|
* - Enforce critical framework conventions that would break the application
|
|
* - Need to provide immediate feedback before code execution
|
|
* - Have been specifically requested to run at manifest-time by framework maintainers
|
|
*
|
|
* DEFAULT: Always return false unless you have explicit permission to do otherwise.
|
|
*/
|
|
public function is_called_during_manifest_scan(): bool
|
|
{
|
|
return false; // Only run during rsx:check, not during manifest build
|
|
}
|
|
|
|
/**
|
|
* Get default severity for this rule
|
|
*/
|
|
public function get_default_severity(): string
|
|
{
|
|
return 'high';
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a route exists using the same logic as Rsx::Route()
|
|
*/
|
|
private function route_exists(string $controller, string $method): bool
|
|
{
|
|
try {
|
|
// Use the same validation logic as Rsx::Route()
|
|
// If this doesn't throw an exception, the route exists
|
|
\App\RSpade\Core\Rsx::Route($controller, $method);
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Check a file for violations
|
|
*/
|
|
public function check(string $file_path, string $contents, array $metadata = []): void
|
|
{
|
|
// Skip if exception comment is present
|
|
if (strpos($contents, '@ROUTE-EXISTS-01-EXCEPTION') !== false) {
|
|
return;
|
|
}
|
|
|
|
// Pattern to match Rsx::Route and Rsx.Route calls (NOT plain Route())
|
|
// Matches both single and double parameter versions:
|
|
// - Rsx::Route('Controller') // PHP, defaults to 'index'
|
|
// - Rsx::Route('Controller', 'method') // PHP
|
|
// - Rsx.Route('Controller') // JavaScript, defaults to 'index'
|
|
// - Rsx.Route('Controller', 'method') // JavaScript
|
|
|
|
// Pattern for two parameters
|
|
$pattern_two_params = '/(?:Rsx::Route|Rsx\.Route)\s*\(\s*[\'"]([^\'"]+)[\'"]\s*,\s*[\'"]([^\'"]+)[\'"]\s*\)/';
|
|
|
|
// Pattern for single parameter (defaults to 'index')
|
|
$pattern_one_param = '/(?:Rsx::Route|Rsx\.Route)\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)/';
|
|
|
|
// First check two-parameter calls
|
|
if (preg_match_all($pattern_two_params, $contents, $matches, PREG_OFFSET_CAPTURE)) {
|
|
foreach ($matches[0] as $index => $match) {
|
|
$full_match = $match[0];
|
|
$offset = $match[1];
|
|
$controller = $matches[1][$index][0];
|
|
$method = $matches[2][$index][0];
|
|
|
|
// Skip if contains template variables like {$variable}
|
|
if (str_contains($controller, '{$') || str_contains($controller, '${') ||
|
|
str_contains($method, '{$') || str_contains($method, '${')) {
|
|
continue;
|
|
}
|
|
|
|
// Skip if method starts with '#' - indicates unimplemented route
|
|
if (str_starts_with($method, '#')) {
|
|
continue;
|
|
}
|
|
|
|
// Skip if this route exists
|
|
if ($this->route_exists($controller, $method)) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate line number
|
|
$line_number = substr_count(substr($contents, 0, $offset), "\n") + 1;
|
|
|
|
// Extract the line for snippet
|
|
$lines = explode("\n", $contents);
|
|
$code_snippet = isset($lines[$line_number - 1]) ? trim($lines[$line_number - 1]) : $full_match;
|
|
|
|
// Build suggestion
|
|
$suggestion = $this->build_suggestion($controller, $method);
|
|
|
|
$this->add_violation(
|
|
$file_path,
|
|
$line_number,
|
|
"Route target does not exist: {$controller}::{$method}",
|
|
$code_snippet,
|
|
$suggestion,
|
|
'high'
|
|
);
|
|
}
|
|
}
|
|
|
|
// Then check single-parameter calls (avoiding overlap with two-parameter calls)
|
|
if (preg_match_all($pattern_one_param, $contents, $matches, PREG_OFFSET_CAPTURE)) {
|
|
foreach ($matches[0] as $index => $match) {
|
|
$full_match = $match[0];
|
|
$offset = $match[1];
|
|
|
|
// Skip if this is actually a two-parameter call (has a comma after the first param)
|
|
$after_match_pos = $offset + strlen($full_match);
|
|
$chars_after = substr($contents, $after_match_pos, 10);
|
|
if (preg_match('/^\s*,/', $chars_after)) {
|
|
continue; // This is a two-parameter call, already handled above
|
|
}
|
|
|
|
$controller = $matches[1][$index][0];
|
|
$method = 'index'; // Default to 'index'
|
|
|
|
// Skip if contains template variables like {$variable}
|
|
if (str_contains($controller, '{$') || str_contains($controller, '${')) {
|
|
continue;
|
|
}
|
|
|
|
// Skip if this route exists
|
|
if ($this->route_exists($controller, $method)) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate line number
|
|
$line_number = substr_count(substr($contents, 0, $offset), "\n") + 1;
|
|
|
|
// Extract the line for snippet
|
|
$lines = explode("\n", $contents);
|
|
$code_snippet = isset($lines[$line_number - 1]) ? trim($lines[$line_number - 1]) : $full_match;
|
|
|
|
// Build suggestion
|
|
$suggestion = $this->build_suggestion($controller, $method);
|
|
|
|
$this->add_violation(
|
|
$file_path,
|
|
$line_number,
|
|
"Route target does not exist: {$controller}::{$method}",
|
|
$code_snippet,
|
|
$suggestion,
|
|
'high'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build suggestion for fixing the violation
|
|
*/
|
|
private function build_suggestion(string $controller, string $method): string
|
|
{
|
|
$suggestions = [];
|
|
|
|
// Simple suggestion since we're using the same validation as Rsx::Route()
|
|
$suggestions[] = "Route target does not exist: {$controller}::{$method}";
|
|
$suggestions[] = "\nTo fix this issue:";
|
|
$suggestions[] = "1. Correct the controller/method names if they're typos";
|
|
$suggestions[] = "2. Implement the missing route if it's a new feature:";
|
|
$suggestions[] = " - Create the controller if it doesn't exist";
|
|
$suggestions[] = " - Add the method with a #[Route] attribute";
|
|
$suggestions[] = "3. Use '#' prefix for unimplemented routes (recommended):";
|
|
$suggestions[] = " - Use Rsx::Route('Controller', '#index') for unimplemented index methods";
|
|
$suggestions[] = " - Use Rsx::Route('Controller', '#method_name') for other unimplemented methods";
|
|
$suggestions[] = " - Routes with '#' prefix will generate '#' URLs and bypass this validation";
|
|
$suggestions[] = " - Example: Rsx::Route('Backend_Users_Controller', '#index')";
|
|
|
|
return implode("\n", $suggestions);
|
|
}
|
|
} |