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>
180 lines
6.6 KiB
PHP
Executable File
180 lines
6.6 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\RSpade\CodeQuality\Rules\PHP;
|
|
|
|
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
|
|
use App\RSpade\Core\Manifest\Manifest;
|
|
|
|
/**
|
|
* Enforces that overridden methods must call parent implementation when marked with #[AuthParentCall]
|
|
*
|
|
* When a parent class method has the #[AuthParentCall] attribute, all subclasses that
|
|
* override this method MUST call parent::methodName() at some point in their implementation.
|
|
* This ensures critical parent functionality is not accidentally skipped.
|
|
*/
|
|
class RequireParentCall_CodeQualityRule extends CodeQualityRule_Abstract
|
|
{
|
|
public function get_id(): string
|
|
{
|
|
return 'PHP-PARENT-CALL-01';
|
|
}
|
|
|
|
public function get_name(): string
|
|
{
|
|
return 'RequireParentCall Enforcement';
|
|
}
|
|
|
|
public function get_description(): string
|
|
{
|
|
return 'Ensures methods marked with #[AuthParentCall] are properly called by overriding subclasses';
|
|
}
|
|
|
|
public function get_file_patterns(): array
|
|
{
|
|
return ['*.php'];
|
|
}
|
|
|
|
public function get_default_severity(): string
|
|
{
|
|
return 'critical';
|
|
}
|
|
|
|
/**
|
|
* This rule runs during manifest scan to check parent call requirements
|
|
*/
|
|
public function is_called_during_manifest_scan(): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Main check method - processes all PHP classes from manifest
|
|
*/
|
|
public function check(string $file_path, string $contents, array $metadata = []): void
|
|
{
|
|
// Only process during manifest-time when we have all files
|
|
static $already_run = false;
|
|
if ($already_run) {
|
|
return;
|
|
}
|
|
|
|
// On the first PHP file, process all files
|
|
if (!empty($metadata) && $metadata['extension'] === 'php') {
|
|
$this->process_all_files();
|
|
$already_run = true;
|
|
}
|
|
}
|
|
|
|
private function process_all_files(): void
|
|
{
|
|
// Get all PHP classes from manifest
|
|
$files = Manifest::get_all();
|
|
|
|
foreach ($files as $file_path => $file_metadata) {
|
|
// Skip non-PHP files
|
|
if (($file_metadata['extension'] ?? '') !== 'php') {
|
|
continue;
|
|
}
|
|
|
|
// Skip files without classes
|
|
if (empty($file_metadata['class'])) {
|
|
continue;
|
|
}
|
|
|
|
$class_name = $file_metadata['class'];
|
|
|
|
// Check both static and instance methods for RequireParentCall attribute
|
|
$methods_to_check = [];
|
|
|
|
if (!empty($file_metadata['public_static_methods'])) {
|
|
foreach ($file_metadata['public_static_methods'] as $method_name => $method_data) {
|
|
$methods_to_check[$method_name] = $method_data;
|
|
}
|
|
}
|
|
|
|
if (!empty($file_metadata['public_instance_methods'])) {
|
|
foreach ($file_metadata['public_instance_methods'] as $method_name => $method_data) {
|
|
$methods_to_check[$method_name] = $method_data;
|
|
}
|
|
}
|
|
|
|
// Check each method for RequireParentCall attribute
|
|
foreach ($methods_to_check as $method_name => $method_data) {
|
|
// Check if this method has RequireParentCall attribute
|
|
if (empty($method_data['attributes']) || !isset($method_data['attributes']['RequireParentCall'])) {
|
|
continue;
|
|
}
|
|
|
|
// This method requires parent call - check all subclasses
|
|
$this->check_subclasses_for_parent_call($class_name, $method_name, $file_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function check_subclasses_for_parent_call(string $parent_class, string $method_name, string $parent_file): void
|
|
{
|
|
// Get all subclasses (including abstract ones)
|
|
$subclasses = Manifest::php_get_subclasses_of($parent_class, false);
|
|
|
|
foreach ($subclasses as $subclass_name) {
|
|
// Get the subclass metadata
|
|
$subclass_data = Manifest::php_get_metadata_by_class($subclass_name);
|
|
|
|
if (!$subclass_data) {
|
|
continue;
|
|
}
|
|
|
|
// Check if subclass overrides this method (in either static or instance methods)
|
|
$subclass_method = null;
|
|
|
|
if (!empty($subclass_data['public_static_methods'][$method_name])) {
|
|
$subclass_method = $subclass_data['public_static_methods'][$method_name];
|
|
} elseif (!empty($subclass_data['public_instance_methods'][$method_name])) {
|
|
$subclass_method = $subclass_data['public_instance_methods'][$method_name];
|
|
}
|
|
|
|
if (!$subclass_method) {
|
|
continue;
|
|
}
|
|
|
|
// Check if the overridden method calls parent
|
|
if (empty($subclass_method['__calls_parent'])) {
|
|
// Get line number if available
|
|
$line = 0;
|
|
|
|
// Get the actual parent FQCN for clearer error message
|
|
$parent_fqcn = Manifest::php_get_metadata_by_class($parent_class)['fqcn'] ?? $parent_class;
|
|
$subclass_fqcn = $subclass_data['fqcn'] ?? $subclass_name;
|
|
|
|
$message = "Method {$subclass_name}::{$method_name}() must call parent::{$method_name}() because {$parent_class}::{$method_name}() is marked with #[AuthParentCall]. ";
|
|
$message .= "The parent method implements critical functionality that must not be skipped in overriding implementations.";
|
|
|
|
$suggestion = "Add a call to parent::{$method_name}() in the {$subclass_name}::{$method_name}() implementation. ";
|
|
$suggestion .= "This ensures the essential parent logic is executed.";
|
|
|
|
// Get code snippet if possible
|
|
$code_snippet = '';
|
|
if (file_exists($subclass_data['file'])) {
|
|
$file_contents = file_get_contents($subclass_data['file']);
|
|
if (preg_match('/public\s+static\s+function\s+' . preg_quote($method_name, '/') . '\s*\([^{]*\{/m', $file_contents, $matches, PREG_OFFSET_CAPTURE)) {
|
|
$offset = $matches[0][1];
|
|
$line = substr_count(substr($file_contents, 0, $offset), "\n") + 1;
|
|
$lines = explode("\n", $file_contents);
|
|
if ($line > 0 && $line <= count($lines)) {
|
|
$code_snippet = trim($lines[$line - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->add_violation(
|
|
$subclass_data['file'],
|
|
$line,
|
|
$message,
|
|
$code_snippet,
|
|
$suggestion,
|
|
'critical'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} |