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>
664 lines
26 KiB
PHP
Executable File
664 lines
26 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\RSpade\CodeQuality\Rules\Manifest;
|
|
|
|
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
|
|
|
|
/**
|
|
* FilenameClassMatch_CodeQualityRule - Enforces filename matches class name
|
|
*
|
|
* Ensures that files containing classes have filenames that match the class name.
|
|
* - app/RSpade: case-sensitive exact match required
|
|
* - rsx: case-insensitive match allowed (snake_case encouraged)
|
|
*/
|
|
class FilenameClassMatch_CodeQualityRule extends CodeQualityRule_Abstract
|
|
{
|
|
public function get_id(): string
|
|
{
|
|
return 'MANIFEST-FILENAME-01';
|
|
}
|
|
|
|
public function get_name(): string
|
|
{
|
|
return 'Filename Class Match';
|
|
}
|
|
|
|
public function get_description(): string
|
|
{
|
|
return 'Ensures filenames match the class names they contain';
|
|
}
|
|
|
|
public function get_file_patterns(): array
|
|
{
|
|
return ['*.php', '*.js'];
|
|
}
|
|
|
|
public function is_called_during_manifest_scan(): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function check(string $file_path, string $contents, array $metadata = []): void
|
|
{
|
|
static $already_checked = false;
|
|
|
|
// Only check once per manifest build
|
|
if ($already_checked) {
|
|
return;
|
|
}
|
|
$already_checked = true;
|
|
|
|
// Check if filename conventions are globally disabled
|
|
if (config('rsx.development.ignore_filename_convention', false)) {
|
|
return;
|
|
}
|
|
|
|
// Get all manifest files
|
|
$files = \App\RSpade\Core\Manifest\Manifest::get_all();
|
|
if (empty($files)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($files as $file => $metadata) {
|
|
// Only check files in ./rsx or ./app/RSpade
|
|
$is_rsx = str_starts_with($file, 'rsx/');
|
|
$is_rspade = str_starts_with($file, 'app/RSpade/');
|
|
|
|
if (!$is_rsx && !$is_rspade) {
|
|
continue;
|
|
}
|
|
|
|
$extension = $metadata['extension'] ?? '';
|
|
$filename = basename($file);
|
|
|
|
// Check PHP/JS files with classes
|
|
if (isset($metadata['class'])) {
|
|
$this->check_class_filename($file, $metadata['class'], $extension, $filename, $is_rsx, $is_rspade);
|
|
}
|
|
|
|
// Check blade.php files with @rsx_id
|
|
if ($extension === 'blade.php' && isset($metadata['id'])) {
|
|
$this->check_blade_filename($file, $metadata['id'], $filename, $is_rsx, $is_rspade);
|
|
}
|
|
|
|
// Check jqhtml files with Define:
|
|
if ($extension === 'jqhtml' && isset($metadata['id'])) {
|
|
$this->check_jqhtml_filename($file, $metadata['id'], $filename, $is_rsx, $is_rspade);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function check_class_filename(string $file, string $class_name, string $extension, string $filename, bool $is_rsx, bool $is_rspade): void
|
|
{
|
|
$filename_without_ext = pathinfo($filename, PATHINFO_FILENAME);
|
|
|
|
// Check if this is a JavaScript class extending Jqhtml_Component
|
|
$is_jqhtml_component = false;
|
|
if ($extension === 'js') {
|
|
try {
|
|
$is_jqhtml_component = \App\RSpade\Core\Manifest\Manifest::js_is_subclass_of($class_name, 'Jqhtml_Component');
|
|
} catch (\Exception $e) {
|
|
// Class not in manifest or not a JS class, treat as regular class
|
|
$is_jqhtml_component = false;
|
|
}
|
|
}
|
|
|
|
// For rsx/ Jqhtml components, allow flexible naming like jqhtml files
|
|
if ($is_rsx && $is_jqhtml_component) {
|
|
// Check exact match (case-insensitive)
|
|
if (strtolower($filename_without_ext) === strtolower($class_name)) {
|
|
return;
|
|
}
|
|
|
|
// Check snake_case variations
|
|
$snake_case = $this->pascal_to_snake_case($class_name);
|
|
if (strtolower($filename_without_ext) === strtolower($snake_case)) {
|
|
return;
|
|
}
|
|
|
|
// Check short name variations
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($class_name, $dir_path);
|
|
if ($short_name !== null) {
|
|
$short_snake = $this->pascal_to_snake_case($short_name);
|
|
if (strtolower($filename_without_ext) === strtolower($short_name) ||
|
|
strtolower($filename_without_ext) === strtolower($short_snake)) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// Regular class filename matching
|
|
$matches_full = $is_rspade
|
|
? $filename_without_ext === $class_name
|
|
: strtolower($filename_without_ext) === strtolower($class_name);
|
|
|
|
if ($matches_full) {
|
|
return; // Perfect match
|
|
}
|
|
|
|
// Check if short filename is valid (directory structure matches prefix)
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($class_name, $dir_path);
|
|
|
|
if ($short_name !== null) {
|
|
$matches_short = $is_rspade
|
|
? $filename_without_ext === $short_name
|
|
: strtolower($filename_without_ext) === strtolower($short_name);
|
|
|
|
if ($matches_short) {
|
|
return; // Valid short name
|
|
}
|
|
}
|
|
}
|
|
|
|
// Violation: filename doesn't match
|
|
// Determine suggested filename
|
|
$suggested_filename = $this->get_suggested_filename_from_remediation(
|
|
$file, $class_name, $extension, $is_rspade, $is_jqhtml_component
|
|
);
|
|
|
|
// Check if we should auto-rename or throw violation
|
|
if ($this->should_auto_rename_or_throw_violation($file, $suggested_filename, $is_rsx)) {
|
|
return; // File was renamed, manifest will restart
|
|
}
|
|
|
|
// Add violation
|
|
if ($is_rspade) {
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Filename '$filename' must match class name '$class_name' (case-sensitive)",
|
|
"class $class_name",
|
|
$this->get_class_remediation($file, $class_name, $filename, $extension, $is_rspade, $is_jqhtml_component),
|
|
'high'
|
|
);
|
|
} else {
|
|
$context = $is_jqhtml_component ? ' (Jqhtml component)' : '';
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Filename '$filename' must match class name '$class_name' (case-insensitive in rsx/){$context}",
|
|
"class $class_name",
|
|
$this->get_class_remediation($file, $class_name, $filename, $extension, $is_rspade, $is_jqhtml_component),
|
|
'medium'
|
|
);
|
|
}
|
|
}
|
|
|
|
private function check_blade_filename(string $file, string $rsx_id, string $filename, bool $is_rsx, bool $is_rspade): void
|
|
{
|
|
$filename_without_blade = str_replace('.blade.php', '', $filename);
|
|
|
|
// Check if filename matches (either full name or short name with matching directory structure)
|
|
$matches_full = $is_rspade
|
|
? $filename_without_blade === $rsx_id
|
|
: strtolower($filename_without_blade) === strtolower($rsx_id);
|
|
|
|
if ($matches_full) {
|
|
return; // Perfect match
|
|
}
|
|
|
|
// Check if short filename is valid (directory structure matches prefix)
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($rsx_id, $dir_path);
|
|
|
|
if ($short_name !== null) {
|
|
$matches_short = $is_rspade
|
|
? $filename_without_blade === $short_name
|
|
: strtolower($filename_without_blade) === strtolower($short_name);
|
|
|
|
if ($matches_short) {
|
|
return; // Valid short name
|
|
}
|
|
}
|
|
|
|
// Violation: filename doesn't match
|
|
// Determine suggested filename
|
|
$suggested_filename = $this->get_suggested_blade_filename_from_remediation($file, $rsx_id, $is_rspade);
|
|
|
|
// Check if we should auto-rename or throw violation
|
|
if ($this->should_auto_rename_or_throw_violation($file, $suggested_filename, $is_rsx)) {
|
|
return; // File was renamed, manifest will restart
|
|
}
|
|
|
|
// Add violation
|
|
if ($is_rspade) {
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Blade filename '$filename' must match @rsx_id '$rsx_id' (case-sensitive)",
|
|
"@rsx_id('$rsx_id')",
|
|
$this->get_blade_remediation($file, $rsx_id, $filename, $is_rspade),
|
|
'high'
|
|
);
|
|
} else {
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Blade filename '$filename' must match @rsx_id '$rsx_id' (case-insensitive in rsx/)",
|
|
"@rsx_id('$rsx_id')",
|
|
$this->get_blade_remediation($file, $rsx_id, $filename, $is_rspade),
|
|
'medium'
|
|
);
|
|
}
|
|
}
|
|
|
|
private function check_jqhtml_filename(string $file, string $component_name, string $filename, bool $is_rsx, bool $is_rspade): void
|
|
{
|
|
$filename_without_ext = pathinfo($filename, PATHINFO_FILENAME);
|
|
|
|
// For rsx/, allow three formats for PascalCase component names:
|
|
// 1. Exact match (TestComponent1)
|
|
// 2. Snake_case with underscores (Test_Component_1)
|
|
// 3. Lowercase with underscores (test_component_1)
|
|
if ($is_rsx) {
|
|
// Check exact match (case-insensitive)
|
|
if (strtolower($filename_without_ext) === strtolower($component_name)) {
|
|
return;
|
|
}
|
|
|
|
// Check snake_case variations
|
|
$snake_case = $this->pascal_to_snake_case($component_name);
|
|
if (strtolower($filename_without_ext) === strtolower($snake_case)) {
|
|
return;
|
|
}
|
|
|
|
// Check short name variations
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($component_name, $dir_path);
|
|
if ($short_name !== null) {
|
|
$short_snake = $this->pascal_to_snake_case($short_name);
|
|
if (strtolower($filename_without_ext) === strtolower($short_name) ||
|
|
strtolower($filename_without_ext) === strtolower($short_snake)) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// app/RSpade: strict case-sensitive match
|
|
if ($filename_without_ext === $component_name) {
|
|
return;
|
|
}
|
|
|
|
// Check short name
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($component_name, $dir_path);
|
|
if ($short_name !== null && $filename_without_ext === $short_name) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Violation: filename doesn't match
|
|
// Determine suggested filename
|
|
$suggested_filename = $this->get_suggested_jqhtml_filename_from_remediation($file, $component_name, $is_rspade);
|
|
|
|
// Check if we should auto-rename or throw violation
|
|
if ($this->should_auto_rename_or_throw_violation($file, $suggested_filename, $is_rsx)) {
|
|
return; // File was renamed, manifest will restart
|
|
}
|
|
|
|
// Add violation
|
|
if ($is_rspade) {
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Jqhtml filename '$filename' must match component name '$component_name' (case-sensitive)",
|
|
"<Define:$component_name>",
|
|
$this->get_jqhtml_remediation($file, $component_name, $filename, $is_rspade),
|
|
'high'
|
|
);
|
|
} else {
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Jqhtml filename '$filename' must match component name '$component_name' (case-insensitive in rsx/)",
|
|
"<Define:$component_name>",
|
|
$this->get_jqhtml_remediation($file, $component_name, $filename, $is_rspade),
|
|
'medium'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert PascalCase to snake_case
|
|
* Inserts underscores before uppercase letters and before first digit in a run of digits
|
|
* Example: TestComponent1 -> Test_Component_1
|
|
*/
|
|
private function pascal_to_snake_case(string $name): string
|
|
{
|
|
// Insert underscore before uppercase letters (except first character)
|
|
$result = preg_replace('/(?<!^)([A-Z])/', '_$1', $name);
|
|
|
|
// Insert underscore before first digit in a run of digits
|
|
$result = preg_replace('/(?<!^)(?<![0-9])([0-9])/', '_$1', $result);
|
|
|
|
// Replace multiple consecutive underscores with single underscore
|
|
$result = preg_replace('/_+/', '_', $result);
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Extract short name from class/id if directory structure matches prefix
|
|
* Returns null if directory structure doesn't match or if short name rules aren't met
|
|
*
|
|
* Rules:
|
|
* - Short names only allowed in ./rsx directory (NOT in /app/RSpade)
|
|
* - Original name must have 3+ segments for short name to be allowed (2-segment names must use full name)
|
|
* - Short name must have 2+ segments (exception: if original was 1 segment, short can be 1 segment)
|
|
*/
|
|
private function extract_short_name(string $full_name, string $dir_path): ?string
|
|
{
|
|
// Short names only allowed in ./rsx directory, not in framework code (/app/RSpade)
|
|
if (str_contains($dir_path, '/app/RSpade')) {
|
|
return null;
|
|
}
|
|
|
|
// Split the full name by underscores
|
|
$name_parts = explode('_', $full_name);
|
|
$original_segment_count = count($name_parts);
|
|
|
|
// If original name has exactly 2 segments, short name is NOT allowed
|
|
if ($original_segment_count === 2) {
|
|
return null;
|
|
}
|
|
|
|
// If only 1 segment, no prefix to match
|
|
if ($original_segment_count === 1) {
|
|
return null;
|
|
}
|
|
|
|
// Split directory path into parts and re-index
|
|
$dir_parts = array_values(array_filter(explode('/', $dir_path)));
|
|
|
|
// Find the maximum number of consecutive matches between end of dir_parts and start of name_parts
|
|
$matched_parts = 0;
|
|
$max_possible = min(count($dir_parts), count($name_parts) - 1);
|
|
|
|
// Try to match last N dir parts with first N name parts
|
|
for ($num_to_check = $max_possible; $num_to_check > 0; $num_to_check--) {
|
|
$all_match = true;
|
|
for ($i = 0; $i < $num_to_check; $i++) {
|
|
$dir_idx = count($dir_parts) - $num_to_check + $i;
|
|
if (strtolower($dir_parts[$dir_idx]) !== strtolower($name_parts[$i])) {
|
|
$all_match = false;
|
|
break;
|
|
}
|
|
}
|
|
if ($all_match) {
|
|
$matched_parts = $num_to_check;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($matched_parts === 0) {
|
|
return null; // No match
|
|
}
|
|
|
|
// Calculate the short name
|
|
$short_parts = array_slice($name_parts, $matched_parts);
|
|
$short_segment_count = count($short_parts);
|
|
|
|
// Validate short name segment count
|
|
// Short name must have 2+ segments (unless original was 1 segment, which we already excluded above)
|
|
if ($short_segment_count < 2) {
|
|
return null; // Short name would be too short
|
|
}
|
|
|
|
return implode('_', $short_parts);
|
|
}
|
|
|
|
private function get_class_remediation(string $file, string $class_name, string $filename, string $extension, bool $is_rspade, bool $is_jqhtml_component = false): string
|
|
{
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($class_name, $dir_path);
|
|
|
|
$message = $is_rspade
|
|
? "Files in app/RSpade/ must have filenames that match the class name (case-sensitive).\n\n"
|
|
: "Files in rsx/ must have filenames that match the class name (case-insensitive).\n\n";
|
|
|
|
$message .= "Class name: $class_name\n";
|
|
$message .= "Current filename: $filename\n\n";
|
|
$message .= "Fix options:\n";
|
|
|
|
// For Jqhtml components in rsx/, use same flexible naming as jqhtml files
|
|
if (!$is_rspade && $is_jqhtml_component) {
|
|
$snake_case = $this->pascal_to_snake_case($class_name);
|
|
$snake_lower = strtolower($snake_case);
|
|
|
|
$options = [];
|
|
|
|
// Always suggest lowercase snake_case first (convention)
|
|
$options[] = [
|
|
'label' => 'RECOMMENDED (RSpade convention)',
|
|
'filename' => $snake_lower . '.' . $extension,
|
|
];
|
|
|
|
// If PascalCase differs from snake_case, offer it as alternative
|
|
if (strtolower($class_name) !== $snake_lower) {
|
|
$options[] = [
|
|
'label' => 'Alternative',
|
|
'filename' => strtolower($class_name) . '.' . $extension,
|
|
];
|
|
}
|
|
|
|
// Add options to message
|
|
$option_num = 1;
|
|
foreach ($options as $option) {
|
|
$message .= "{$option_num}. {$option['label']}: Rename to '{$option['filename']}'\n";
|
|
$message .= " mv '$filename' '{$option['filename']}'\n\n";
|
|
$option_num++;
|
|
}
|
|
} else {
|
|
// Regular class naming
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . strtolower($short_name) . '.' . $extension)) {
|
|
$short_filename = $is_rspade ? $short_name . '.' . $extension : strtolower($short_name) . '.' . $extension;
|
|
$message .= "1. RECOMMENDED (short name): Rename to '$short_filename'\n";
|
|
$message .= " mv '$filename' '$short_filename'\n\n";
|
|
$full_filename = $is_rspade ? $class_name . '.' . $extension : strtolower($class_name) . '.' . $extension;
|
|
$message .= "2. Full name: Rename to '$full_filename'\n";
|
|
$message .= " mv '$filename' '$full_filename'\n\n";
|
|
} else {
|
|
$full_filename = $is_rspade ? $class_name . '.' . $extension : strtolower($class_name) . '.' . $extension;
|
|
$message .= "1. Rename to '$full_filename'\n";
|
|
$message .= " mv '$filename' '$full_filename'\n\n";
|
|
}
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
private function get_blade_remediation(string $file, string $rsx_id, string $filename, bool $is_rspade): string
|
|
{
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($rsx_id, $dir_path);
|
|
|
|
$message = $is_rspade
|
|
? "Blade files in app/RSpade/ must have filenames that match the @rsx_id (case-sensitive).\n\n"
|
|
: "Blade files in rsx/ must have filenames that match the @rsx_id (case-insensitive).\n\n";
|
|
|
|
$message .= "@rsx_id: $rsx_id\n";
|
|
$message .= "Current filename: $filename\n\n";
|
|
$message .= "Fix options:\n";
|
|
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . strtolower($short_name) . '.blade.php')) {
|
|
$short_filename = $is_rspade ? $short_name . '.blade.php' : strtolower($short_name) . '.blade.php';
|
|
$message .= "1. RECOMMENDED (short name): Rename to '$short_filename'\n";
|
|
$message .= " mv '$filename' '$short_filename'\n\n";
|
|
$full_filename = $is_rspade ? $rsx_id . '.blade.php' : strtolower($rsx_id) . '.blade.php';
|
|
$message .= "2. Full name: Rename to '$full_filename'\n";
|
|
$message .= " mv '$filename' '$full_filename'\n\n";
|
|
} else {
|
|
$full_filename = $is_rspade ? $rsx_id . '.blade.php' : strtolower($rsx_id) . '.blade.php';
|
|
$message .= "1. Rename to '$full_filename'\n";
|
|
$message .= " mv '$filename' '$full_filename'\n\n";
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
private function get_jqhtml_remediation(string $file, string $component_name, string $filename, bool $is_rspade): string
|
|
{
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($component_name, $dir_path);
|
|
|
|
$message = $is_rspade
|
|
? "Jqhtml files in app/RSpade/ must have filenames that match the component name (case-sensitive).\n\n"
|
|
: "Jqhtml files in rsx/ must have filenames that match the component name (case-insensitive).\n\n";
|
|
|
|
$message .= "Component name: $component_name\n";
|
|
$message .= "Current filename: $filename\n\n";
|
|
$message .= "Fix options:\n";
|
|
|
|
if ($is_rspade) {
|
|
// app/RSpade: case-sensitive exact match only
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . $short_name . '.jqhtml')) {
|
|
$message .= "1. RECOMMENDED (short name): Rename to '{$short_name}.jqhtml'\n";
|
|
$message .= " mv '$filename' '{$short_name}.jqhtml'\n\n";
|
|
$message .= "2. Full name: Rename to '{$component_name}.jqhtml'\n";
|
|
$message .= " mv '$filename' '{$component_name}.jqhtml'\n\n";
|
|
} else {
|
|
$message .= "1. Rename to '{$component_name}.jqhtml'\n";
|
|
$message .= " mv '$filename' '{$component_name}.jqhtml'\n\n";
|
|
}
|
|
} else {
|
|
// rsx/: Allow PascalCase or snake_case (lowercase with underscores is convention)
|
|
$snake_case = $this->pascal_to_snake_case($component_name);
|
|
$snake_lower = strtolower($snake_case);
|
|
|
|
// Determine which options to show
|
|
$options = [];
|
|
|
|
// Always suggest lowercase snake_case first (convention)
|
|
$options[] = [
|
|
'label' => 'RECOMMENDED (RSpade convention)',
|
|
'filename' => $snake_lower . '.jqhtml',
|
|
];
|
|
|
|
// If PascalCase differs from snake_case, offer it as alternative
|
|
if (strtolower($component_name) !== $snake_lower) {
|
|
$options[] = [
|
|
'label' => 'Alternative',
|
|
'filename' => strtolower($component_name) . '.jqhtml',
|
|
];
|
|
}
|
|
|
|
// Add options to message
|
|
$option_num = 1;
|
|
foreach ($options as $option) {
|
|
$message .= "{$option_num}. {$option['label']}: Rename to '{$option['filename']}'\n";
|
|
$message .= " mv '$filename' '{$option['filename']}'\n\n";
|
|
$option_num++;
|
|
}
|
|
}
|
|
|
|
return $message;
|
|
}
|
|
|
|
/**
|
|
* Check if file should be auto-renamed or if violation should be thrown
|
|
* Returns true if file was renamed (signals manifest restart needed)
|
|
* Returns false if violation should be added
|
|
*/
|
|
private function should_auto_rename_or_throw_violation(string $file, string $suggested_filename, bool $is_rsx): bool
|
|
{
|
|
// Check if file has @FILENAME-CONVENTION-EXCEPTION marker
|
|
$file_contents = file_get_contents(base_path($file));
|
|
if (str_contains($file_contents, '@FILENAME-CONVENTION-EXCEPTION')) {
|
|
return true; // Skip this file entirely (no violation, no rename)
|
|
}
|
|
|
|
// Check if auto-rename is enabled and file is in rsx/
|
|
if (!config('rsx.development.auto_rename_files', false) || !$is_rsx) {
|
|
return false; // Throw normal violation
|
|
}
|
|
|
|
// Check if target filename already exists
|
|
$target_path = dirname($file) . '/' . $suggested_filename;
|
|
if (file_exists(base_path($target_path))) {
|
|
return false; // Conflict - throw normal violation
|
|
}
|
|
|
|
// Perform rename
|
|
$old_path = base_path($file);
|
|
$new_path = base_path($target_path);
|
|
|
|
rename($old_path, $new_path);
|
|
console_debug('MANIFEST', "Auto-renamed: {$file} → {$target_path}");
|
|
|
|
// Signal manifest to restart
|
|
\App\RSpade\Core\Manifest\Manifest::flag_needs_restart();
|
|
|
|
return true; // File was renamed, no violation needed
|
|
}
|
|
|
|
/**
|
|
* Extract suggested filename for class files from remediation logic
|
|
*/
|
|
private function get_suggested_filename_from_remediation(string $file, string $class_name, string $extension, bool $is_rspade, bool $is_jqhtml_component): string
|
|
{
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($class_name, $dir_path);
|
|
|
|
if ($is_rspade) {
|
|
// app/RSpade: case-sensitive
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . $short_name . '.' . $extension)) {
|
|
return $short_name . '.' . $extension;
|
|
}
|
|
return $class_name . '.' . $extension;
|
|
} else {
|
|
// rsx/: For Jqhtml components, use snake_case
|
|
if ($is_jqhtml_component) {
|
|
$snake_case = $this->pascal_to_snake_case($class_name);
|
|
return strtolower($snake_case) . '.' . $extension;
|
|
}
|
|
|
|
// Regular classes
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . strtolower($short_name) . '.' . $extension)) {
|
|
return strtolower($short_name) . '.' . $extension;
|
|
}
|
|
return strtolower($class_name) . '.' . $extension;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract suggested filename for blade files from remediation logic
|
|
*/
|
|
private function get_suggested_blade_filename_from_remediation(string $file, string $rsx_id, bool $is_rspade): string
|
|
{
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($rsx_id, $dir_path);
|
|
|
|
if ($is_rspade) {
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . $short_name . '.blade.php')) {
|
|
return $short_name . '.blade.php';
|
|
}
|
|
return $rsx_id . '.blade.php';
|
|
} else {
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . strtolower($short_name) . '.blade.php')) {
|
|
return strtolower($short_name) . '.blade.php';
|
|
}
|
|
return strtolower($rsx_id) . '.blade.php';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract suggested filename for jqhtml files from remediation logic
|
|
*/
|
|
private function get_suggested_jqhtml_filename_from_remediation(string $file, string $component_name, bool $is_rspade): string
|
|
{
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($component_name, $dir_path);
|
|
|
|
if ($is_rspade) {
|
|
if ($short_name !== null && !file_exists(dirname(base_path($file)) . '/' . $short_name . '.jqhtml')) {
|
|
return $short_name . '.jqhtml';
|
|
}
|
|
return $component_name . '.jqhtml';
|
|
} else {
|
|
// rsx/: use snake_case (lowercase with underscores)
|
|
$snake_case = $this->pascal_to_snake_case($component_name);
|
|
return strtolower($snake_case) . '.jqhtml';
|
|
}
|
|
}
|
|
}
|