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>
260 lines
9.8 KiB
PHP
Executable File
260 lines
9.8 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\RSpade\CodeQuality\Rules\Convention;
|
|
|
|
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
|
|
|
|
/**
|
|
* FilenameRedundantPrefix_CodeQualityRule - Detects unnecessarily long filenames
|
|
*
|
|
* Suggests using short filenames when the directory structure already contains the prefix.
|
|
*/
|
|
class FilenameRedundantPrefix_CodeQualityRule extends CodeQualityRule_Abstract
|
|
{
|
|
public function get_id(): string
|
|
{
|
|
return 'CONV-FILENAME-01';
|
|
}
|
|
|
|
public function get_name(): string
|
|
{
|
|
return 'Filename Redundant Prefix Convention';
|
|
}
|
|
|
|
public function get_description(): string
|
|
{
|
|
return 'Suggests using short filenames when directory structure contains the prefix';
|
|
}
|
|
|
|
public function get_file_patterns(): array
|
|
{
|
|
return ['*.php', '*.js', '*.jqhtml', '*.blade.php'];
|
|
}
|
|
|
|
public function get_default_severity(): string
|
|
{
|
|
return 'convention';
|
|
}
|
|
|
|
public function check(string $file_path, string $contents, array $metadata = []): void
|
|
{
|
|
// Only check files in ./rsx or ./app/RSpade
|
|
$relative_path = str_replace(base_path() . '/', '', $file_path);
|
|
$is_rsx = str_starts_with($relative_path, 'rsx/');
|
|
$is_rspade = str_starts_with($relative_path, 'app/RSpade/');
|
|
|
|
if (!$is_rsx && !$is_rspade) {
|
|
return;
|
|
}
|
|
|
|
$extension = $metadata['extension'] ?? '';
|
|
$filename = basename($file_path);
|
|
|
|
// Check PHP/JS files with classes
|
|
if (isset($metadata['class'])) {
|
|
$this->check_class_redundancy($relative_path, $metadata['class'], $extension, $filename, $is_rspade);
|
|
}
|
|
|
|
// Check blade.php files with @rsx_id
|
|
if ($extension === 'blade.php' && isset($metadata['id'])) {
|
|
$this->check_blade_redundancy($relative_path, $metadata['id'], $filename, $is_rspade);
|
|
}
|
|
|
|
// Check jqhtml files with Define:
|
|
if ($extension === 'jqhtml' && isset($metadata['id'])) {
|
|
$this->check_jqhtml_redundancy($relative_path, $metadata['id'], $filename, $is_rspade);
|
|
}
|
|
}
|
|
|
|
private function check_class_redundancy(string $file, string $class_name, string $extension, string $filename, bool $is_rspade): void
|
|
{
|
|
$filename_without_ext = pathinfo($filename, PATHINFO_FILENAME);
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($class_name, $dir_path);
|
|
|
|
if ($short_name === null) {
|
|
return; // No short name available
|
|
}
|
|
|
|
// Check if current filename is the full name (redundant)
|
|
$is_full_name = $is_rspade
|
|
? $filename_without_ext === $class_name
|
|
: strtolower($filename_without_ext) === strtolower($class_name);
|
|
|
|
if (!$is_full_name) {
|
|
return; // Not using full name
|
|
}
|
|
|
|
// Check if short filename would be available
|
|
$short_filename = $is_rspade ? $short_name . '.' . $extension : strtolower($short_name) . '.' . $extension;
|
|
$short_path = dirname(base_path($file)) . '/' . $short_filename;
|
|
|
|
if (file_exists($short_path)) {
|
|
return; // Short name already taken
|
|
}
|
|
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Filename contains redundant prefix already represented in directory structure",
|
|
"class $class_name",
|
|
"Directory structure already contains the class name prefix.\n" .
|
|
"Consider using the shorter filename: '$short_filename'\n" .
|
|
" mv '$filename' '$short_filename'\n\n" .
|
|
"IMPORTANT: Only the filename should be changed. The class name must remain unchanged.\n" .
|
|
"The directory structure provides the necessary context for the shorter filename.\n" .
|
|
"Example: rsx/app/demo/demo_controller.php → rsx/app/demo/controller.php\n" .
|
|
" (but class Demo_Controller remains Demo_Controller)\n\n" .
|
|
"This improves readability while maintaining uniqueness.",
|
|
'convention'
|
|
);
|
|
}
|
|
|
|
private function check_blade_redundancy(string $file, string $rsx_id, string $filename, bool $is_rspade): void
|
|
{
|
|
$filename_without_blade = str_replace('.blade.php', '', $filename);
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($rsx_id, $dir_path);
|
|
|
|
if ($short_name === null) {
|
|
return;
|
|
}
|
|
|
|
$is_full_name = $is_rspade
|
|
? $filename_without_blade === $rsx_id
|
|
: strtolower($filename_without_blade) === strtolower($rsx_id);
|
|
|
|
if (!$is_full_name) {
|
|
return;
|
|
}
|
|
|
|
$short_filename = $is_rspade ? $short_name . '.blade.php' : strtolower($short_name) . '.blade.php';
|
|
$short_path = dirname(base_path($file)) . '/' . $short_filename;
|
|
|
|
if (file_exists($short_path)) {
|
|
return;
|
|
}
|
|
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Blade filename contains redundant prefix already represented in directory structure",
|
|
"@rsx_id('$rsx_id')",
|
|
"Directory structure already contains the @rsx_id prefix.\n" .
|
|
"Consider using the shorter filename: '$short_filename'\n" .
|
|
" mv '$filename' '$short_filename'\n\n" .
|
|
"IMPORTANT: Only the filename should be changed. The @rsx_id must remain unchanged.\n" .
|
|
"The directory structure provides the necessary context for the shorter filename.\n" .
|
|
"Example: rsx/app/demo/sections/demo_sections_cards.blade.php → cards.blade.php\n" .
|
|
" (but @rsx_id('demo.sections.cards') remains demo.sections.cards)\n\n" .
|
|
"This improves readability while maintaining uniqueness.",
|
|
'convention'
|
|
);
|
|
}
|
|
|
|
private function check_jqhtml_redundancy(string $file, string $component_name, string $filename, bool $is_rspade): void
|
|
{
|
|
$filename_without_ext = pathinfo($filename, PATHINFO_FILENAME);
|
|
$dir_path = dirname($file);
|
|
$short_name = $this->extract_short_name($component_name, $dir_path);
|
|
|
|
if ($short_name === null) {
|
|
return;
|
|
}
|
|
|
|
$is_full_name = $is_rspade
|
|
? $filename_without_ext === $component_name
|
|
: strtolower($filename_without_ext) === strtolower($component_name);
|
|
|
|
if (!$is_full_name) {
|
|
return;
|
|
}
|
|
|
|
$short_filename = $is_rspade ? $short_name . '.jqhtml' : strtolower($short_name) . '.jqhtml';
|
|
$short_path = dirname(base_path($file)) . '/' . $short_filename;
|
|
|
|
if (file_exists($short_path)) {
|
|
return;
|
|
}
|
|
|
|
$this->add_violation(
|
|
$file,
|
|
1,
|
|
"Jqhtml filename contains redundant prefix already represented in directory structure",
|
|
"<Define:$component_name>",
|
|
"Directory structure already contains the component name prefix.\n" .
|
|
"Consider using the shorter filename: '$short_filename'\n" .
|
|
" mv '$filename' '$short_filename'\n\n" .
|
|
"IMPORTANT: Only the filename should be changed. The component name must remain unchanged.\n" .
|
|
"The directory structure provides the necessary context for the shorter filename.\n" .
|
|
"Example: rsx/app/demo/components/demo_card.jqhtml → card.jqhtml\n" .
|
|
" (but <Define:Demo_Card> remains Demo_Card)\n\n" .
|
|
"This improves readability while maintaining uniqueness.",
|
|
'convention'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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:
|
|
* - 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
|
|
{
|
|
$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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|