Files
rspade_system/app/RSpade/CodeQuality/Rules/Convention/OneBundlePerModule_CodeQualityRule.php
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
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>
2025-10-21 02:08:33 +00:00

117 lines
4.0 KiB
PHP
Executable File

<?php
namespace App\RSpade\CodeQuality\Rules\Convention;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
use App\RSpade\Core\Manifest\Manifest;
class OneBundlePerModule_CodeQualityRule extends CodeQualityRule_Abstract
{
public function get_id(): string
{
return 'CONV-BUNDLE-03';
}
public function get_name(): string
{
return 'One Bundle Per Module Directory Convention';
}
public function get_description(): string
{
return 'Module directories should have only one bundle (./rsx/app root can have multiple)';
}
public function get_file_patterns(): array
{
return ['*.php'];
}
public function get_default_severity(): string
{
return 'critical';
}
/**
* Whether this rule is called during manifest scan
*
* EXCEPTION: This rule has been explicitly approved to run at manifest-time because
* bundle organization is a critical framework convention for module structure.
*/
public function is_called_during_manifest_scan(): bool
{
return true; // Explicitly approved for manifest-time checking
}
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Skip if not a PHP class file
if (!isset($metadata['class'])) {
return;
}
// Check if class extends Rsx_Bundle_Abstract
$extends = $metadata['extends'] ?? '';
if ($extends !== 'Rsx_Bundle_Abstract' &&
$extends !== 'App\\RSpade\\Core\\Bundle\\Rsx_Bundle_Abstract') {
return;
}
// Get relative path from base
$relative_path = str_replace(base_path() . '/', '', $file_path);
$dir_path = dirname($relative_path);
// Skip if bundle is directly in rsx/app (they can have multiple)
if ($dir_path === 'rsx/app') {
return;
}
// Skip if not in rsx/app
if (!str_starts_with($dir_path, 'rsx/app/')) {
return;
}
// Check the manifest for other bundles in the same directory
$manifest = Manifest::get_all();
$bundles_in_same_dir = [];
foreach ($manifest as $path => $file_metadata) {
// Check if it's a PHP file in the same directory
if (dirname($path) !== $dir_path) {
continue;
}
// Check if it's a bundle class
if (isset($file_metadata['class']) && isset($file_metadata['extends'])) {
$file_extends = $file_metadata['extends'];
if ($file_extends === 'Rsx_Bundle_Abstract' ||
$file_extends === 'App\\RSpade\\Core\\Bundle\\Rsx_Bundle_Abstract') {
$bundles_in_same_dir[] = basename($path, '.php');
}
}
}
// If there's more than one bundle in this directory, throw an exception
if (count($bundles_in_same_dir) > 1) {
$error_message = "Code Quality Violation (CONV-BUNDLE-03) - Multiple Bundles in Same Directory\n\n";
$error_message .= "Module directory '{$dir_path}' has multiple bundle files:\n";
foreach ($bundles_in_same_dir as $bundle_name) {
$error_message .= " - {$bundle_name}.php\n";
}
$error_message .= "\nCRITICAL: Each module directory should have only ONE bundle.\n\n";
$error_message .= "Resolution:\n";
$error_message .= "1. Consolidate these bundles into a single bundle file\n";
$error_message .= "2. OR move extra bundles to their own module directories\n";
$error_message .= "3. OR move them to ./rsx/app/ (which allows multiple bundles)\n\n";
$error_message .= "This convention ensures clean module organization and predictable bundle loading.";
throw new \App\RSpade\CodeQuality\RuntimeChecks\YoureDoingItWrongException(
$error_message,
0,
null,
$file_path,
1
);
}
}
}