$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); } }