🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
17 KiB
Executable File
RSpade Code Quality System
Overview
The Code Quality system is a modular, extensible framework for enforcing coding standards and best practices across the RSpade codebase. It replaces a monolithic 1921-line checker with a clean, maintainable architecture using Manifest-based auto-discovery.
Architecture
Core Components
-
CodeQualityChecker (
CodeQualityChecker.php)- Main orchestrator that discovers and runs all rules
- Auto-discovers rules via RuleDiscovery::discover_rules()
- Handles file scanning, caching, and violation collection
- Performs syntax linting for PHP, JavaScript, and JSON files
-
CodeQualityRule_Abstract (
Rules/CodeQualityRule_Abstract.php)- Base class for all code quality rules
- Defines the interface:
get_id(),get_name(),check(), etc. - Provides
add_violation()helper method - Rules self-register by extending this class
-
Violation (
Violation.php)- Data class representing a code violation
- Contains: rule_id, file_path, line_number, message, severity, code_snippet, suggestion
- Provides
to_array()for serialization
Support Classes
- ViolationCollector - Aggregates violations from all rules
- CacheManager - Caches sanitized file contents to improve performance
- FileSanitizer - Removes comments and strings for accurate code analysis
Rule Categories
PHP Rules (Rules/PHP/)
-
NamingConventionRule (PHP-NAMING-01)
- Enforces underscore_case for methods and variables
- Excludes Laravel framework methods (toArray, firstOrCreate, etc.)
- Severity: Medium
-
MassAssignmentRule (PHP-MASS-01)
- Prohibits use of $fillable property
- Ensures $guarded = ['*'] or removal
- Severity: High
-
PhpFallbackLegacyRule (PHP-FALLBACK-01)
- Detects "fallback" or "legacy" in comments/function names
- Enforces fail-loud principle
- Severity: Critical
-
DbTableUsageRule (PHP-DB-01)
- Prohibits DB::table() usage
- Requires ORM models for database access
- Severity: High
-
FunctionExistsRule (PHP-FUNC-01)
- Prohibits function_exists() checks
- Enforces predictable runtime environment
- Severity: High
Jqhtml Rules (Rules/Jqhtml/)
- JqhtmlInlineScriptRule (JQHTML-INLINE-01)
- Prohibits inline <script> and <style> tags in .jqhtml templates
- Enforces component class pattern with Component
- Requires separate .js and .scss files
- Severity: Critical
- Runs at manifest-time
JavaScript Rules (Rules/JavaScript/)
-
VarUsageRule (JS-VAR-01)
- Prohibits 'var' keyword, requires let/const
- Severity: Medium
-
DefensiveCodingRule (JS-DEFENSIVE-01)
- Prohibits typeof checks for core classes
- Core classes always exist in runtime
- Severity: High
-
InstanceMethodsRule (JS-STATIC-01)
- Enforces static methods in JavaScript classes
- Exceptions allowed with @instance-class comment
- Severity: Medium
-
JQueryUsageRule (JS-JQUERY-01)
- Enforces $ over jQuery
- Detects deprecated methods (live, die, bind, etc.)
- Severity: Medium
-
ThisUsageRule (JS-THIS-01)
- Detects problematic 'this' usage
- Suggests class reference pattern
- Severity: Medium
-
DocumentReadyRule (JS-READY-01)
- Prohibits jQuery ready patterns
- Requires ES6 class with static init()
- Severity: High
-
JsFallbackLegacyRule (JS-FALLBACK-01)
- JavaScript version of fallback/legacy detection
- Severity: Critical
Common Rules (Rules/Common/)
-
FilenameCaseRule (FILE-CASE-01)
- Enforces lowercase filenames
- Severity: Low
-
FilenameEnhancedRule (FILE-NAME-01)
- Validates controller/model naming conventions
- Checks file-class name consistency
- Severity: Medium
-
RootFilesRule (FILE-ROOT-01)
- Restricts files in project root
- Maintains clean project structure
- Severity: Medium
-
RsxTestFilesRule (FILE-RSX-01)
- Prevents test files directly in rsx/
- Enforces proper test organization
- Severity: Medium
-
RouteExistsRule (ROUTE-EXISTS-01)
- Validates Rsx::Route() calls reference existing routes
- Checks controller/method combinations exist in manifest
- Suggests placeholder URLs for unimplemented routes
- Severity: High
Manifest Rules (Rules/Manifest/)
-
SpaAttributeMisuseRule (PHP-SPA-01)
- Detects #[SPA] combined with #[Route] on same method
- #[SPA] is for bootstrap entry points only, not route definitions
- Routes in SPA modules are defined in JavaScript actions with @route()
- Runs at manifest-time for immediate feedback
- Severity: Critical
-
InstanceMethodsRule (MANIFEST-INST-01)
- Enforces static-only classes unless marked Instantiatable
- Checks both PHP (#[Instantiatable]) and JS (@Instantiatable)
- Walks inheritance chain to check ancestors
- Severity: Medium
Sanity Check Rules (Rules/SanityChecks/)
- PhpSanityCheckRule (PHP-SC-001)
- Complex pattern detection (currently disabled)
- Detects suspicious code patterns
- Severity: Critical
Configuration
Config File (config/rsx.php)
'code_quality' => [
'enabled' => env('CODE_QUALITY_ENABLED', true),
'cache_enabled' => true,
'parallel_processing' => false,
'excluded_directories' => [
'vendor',
'node_modules',
'storage',
'bootstrap/cache',
'CodeQuality', // Exclude checker itself
],
'rsx_test_whitelist' => [
// Files allowed in rsx/ directory
'main.php',
'routes.php',
],
],
Disabling Rules
Rules can be disabled by adding them to the disabled list:
'disabled_rules' => [
'PHP-SC-001', // Temporarily disabled
],
Usage
Command Line
# Run all checks
php artisan rsx:check
# Check specific directory
php artisan rsx:check rsx/
# Check specific file
php artisan rsx:check app/Models/User.php
Exception Granting System
The code quality system supports granting exceptions to allow specific violations when justified. Exceptions are granted via specially formatted comments in the source files.
Exception Comment Format
@{RULE-ID}-EXCEPTION - Optional rationale
Naming Convention:
- Use the exact rule ID from
get_id()method - Add
-EXCEPTIONsuffix - Examples:
@PHP-NAMING-01-EXCEPTION,@JS-DEFENSIVE-01-EXCEPTION,@FILE-CASE-01-EXCEPTION
Exception Placement
Line-Level Exceptions (most common): Place the exception comment on the same line as the violation OR on the line immediately before it.
// Same-line exception
if (key && key.startsWith('rsx::')) { // @JS-DEFENSIVE-01-EXCEPTION - storage.key() can return null
// Previous-line exception
// @PHP-REFLECT-01-EXCEPTION - Test runner needs ReflectionClass for filtering
if ($reflection->isAbstract()) {
continue;
}
File-Level Exceptions (for entire file): Place at the top of the file, after namespace/use statements, before the main docblock.
<?php
namespace App\RSpade\Core\Ajax;
use SomeClass;
// @FILE-SUBCLASS-01-EXCEPTION
/**
* Class docblock
*/
class MyClass {
Docblock Exceptions (for method/class): Place inside the docblock using JSDoc/PHPDoc style.
/**
* Check if a method is overriding a parent method
*
* @PHP-REFLECT-02-EXCEPTION: This method needs ReflectionClass to check parent methods
* from Laravel framework classes which are not tracked in the manifest.
*/
protected function is_overriding_parent_method($class_name, $method_name) {
Implementing Exception Handling in Rules
NOT all rules implement exception handling - it must be added per-rule. Approximately 31 of 111 rules currently support exceptions.
To check if a rule supports exceptions:
- Open the rule file in
/system/app/RSpade/CodeQuality/Rules/ - Search for
EXCEPTIONin the file - If found, the rule implements exception checking
- If not found, exceptions will be ignored by that rule
To implement exception handling in a rule:
Add a check before calling add_violation(). The exact implementation depends on rule structure:
Line-by-line checking pattern:
public function check(string $file_path, string $contents, array $metadata = []): void
{
$lines = explode("\n", $contents);
foreach ($lines as $line_number => $line) {
$actual_line_number = $line_number + 1;
// Skip if line has exception comment
if (str_contains($line, '@' . $this->get_id() . '-EXCEPTION')) {
continue;
}
// Check for violation
if ($this->detect_violation($line)) {
$this->add_violation(...);
}
}
}
Multi-line or previous-line pattern:
// Check previous line for exception comment
$prev_line_index = $line_number - 1;
if ($prev_line_index >= 0 && str_contains($lines[$prev_line_index], '@' . $this->get_id() . '-EXCEPTION')) {
continue; // Skip this line
}
File-level pattern:
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Check if entire file has exception
if (str_contains($contents, '@' . $this->get_id() . '-EXCEPTION')) {
return; // Skip entire file
}
// ... rest of checking logic
}
Exception Rationale Guidelines
Always include a rationale explaining WHY the exception is needed:
// @JS-DEFENSIVE-01-EXCEPTION - Browser API storage.key(i) can return null when i >= storage.length
Good rationales:
- Reference external API behavior: "Browser API returns null", "Laravel method signature requires this"
- Explain architectural necessity: "Task system uses direct queries for performance"
- Note optional/polymorphic patterns: "Array.find() returns undefined when no match"
Bad rationales:
- "TODO: fix later"
- "Not sure why this is needed"
- No rationale at all
CRITICAL: AI Agent Exception Policy
ABSOLUTE PROHIBITION - NEVER GRANT EXCEPTIONS AUTONOMOUSLY
When you encounter a code quality violation, you are FORBIDDEN from granting exceptions without explicit programmer approval.
Required procedure:
- STOP - Do not add exception comments
- ANALYZE - Determine if the violation is:
- Invalid defensive coding (should be removed)
- Valid duck typing (needs exception)
- External API constraint (needs exception)
- REPORT - Present findings: "Found violation X in file Y. Analysis: [your reasoning]. Options: a) Remove the check (fail-loud), b) Grant exception (provide rationale), c) Refactor differently"
- WAIT - Wait for programmer to decide
- NEVER add
@RULE-ID-EXCEPTIONcomments without explicit approval
Only grant exceptions when:
- Programmer explicitly requests it: "grant an exception for this"
- Programmer approves your recommendation: "yes, add the exception"
- You are implementing a fix that the programmer has already approved
Exception grants are permanent code changes - they suppress violations indefinitely and become part of the codebase. Do not make this decision autonomously.
Development
Creating New Rules
- Create a new class extending
CodeQualityRule_Abstract - Place in appropriate Rules subdirectory
- Implement required methods:
get_id()- Unique rule identifierget_name()- Human-readable namecheck()- Violation detection logic
- Add to Manifest scan directories if needed
Example:
namespace App\RSpade\CodeQuality\Rules\PHP;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
class MyNew_CodeQualityRule extends CodeQualityRule_Abstract
{
public function get_id(): string
{
return 'PHP-NEW-01';
}
public function get_name(): string
{
return 'My New Rule';
}
public function get_description(): string
{
return 'Description of what this rule checks';
}
public function get_file_patterns(): array
{
return ['*.php'];
}
/**
* Whether this rule is called during manifest scan
*
* IMPORTANT: This method should ALWAYS return false unless explicitly requested
* by the framework developer. Manifest-time checks are reserved for critical
* framework convention violations that need immediate developer attention.
*
* Rules executed during manifest scan will run on every file change in development,
* potentially impacting performance. Only enable this for rules that:
* - Enforce critical framework conventions that would break the application
* - Need to provide immediate feedback before code execution
* - Have been specifically requested to run at manifest-time by framework maintainers
*
* DEFAULT: Always return false unless you have explicit permission to do otherwise.
*/
public function is_called_during_manifest_scan(): bool
{
return false; // Always false unless explicitly approved
}
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Detection logic
if ($violation_found) {
$this->add_violation(
$file_path,
$line_number,
"Violation message",
$code_snippet,
"How to fix",
'medium'
);
}
}
}
Testing Rules
- Create a temporary test file with violations
- Run
php artisan rsx:check - Verify violations are detected correctly
- Clean up test files
Migration from Monolith
The original 1921-line CodeStandardsChecker.php has been:
- Archived to
/archived/CodeStandardsChecker.old.php - Split into modular rule classes
- Enhanced with auto-discovery via Manifest
- Improved with better caching and performance
All original rule logic has been preserved exactly, ensuring no regression in code quality checks.
Performance
- Caching: Sanitized file contents are cached to avoid repeated processing
- Incremental Linting: Files are only linted if changed since last check
- Efficient Scanning: Smart directory traversal skips excluded paths
Manifest-Time Checking
By default, code quality rules run only when php artisan rsx:check is executed. However, certain critical rules can be configured to run during manifest builds to provide immediate feedback.
When to Enable Manifest-Time Checking
DO NOT enable manifest-time checking unless you have explicit approval. Rules should only run at manifest-time if they:
- Enforce critical framework conventions that would break the application
- Need to provide immediate feedback before code execution
- Have been specifically requested by framework maintainers
Incremental vs Cross-File Rules
Manifest-time rules support two processing modes via is_incremental():
Incremental Rules (is_incremental() = true, default):
- Check each file independently
- Only changed files are processed during incremental manifest rebuilds
- More efficient for per-file validation (syntax, patterns, structure)
- Example:
JqhtmlInlineScriptRule,MassAssignmentRule
Cross-File Rules (is_incremental() = false):
- Need to see relationships between files or check the full manifest
- Run once per manifest build with access to
Manifest::get_all() - Use for rules that validate naming across files, check for duplicates, etc.
- Example:
ScssClassScope_CodeQualityRule,InstanceMethods_CodeQualityRule
To make a rule cross-file, override is_incremental():
public function is_incremental(): bool
{
return false; // This rule needs cross-file context
}
Cross-file rules should use a static guard to ensure they only run once:
public function check(string $file_path, string $contents, array $metadata = []): void
{
static $already_checked = false;
if ($already_checked) return;
$already_checked = true;
// Access all files via Manifest::get_all()
$files = Manifest::get_all();
// ... check relationships between files
}
Current Manifest-Time Rules
Only the following rules are approved for manifest-time execution:
- BLADE-SCRIPT-01 (InlineScriptRule): Prevents inline JavaScript in Blade files (critical architecture violation)
- JQHTML-INLINE-01 (JqhtmlInlineScriptRule): Prevents inline scripts/styles in Jqhtml template files (critical architecture violation)
- PHP-SPA-01 (SpaAttributeMisuseRule): Prevents combining #[SPA] with #[Route] attributes (critical architecture misunderstanding)
- MANIFEST-INST-01 (InstanceMethodsRule): Enforces static-only classes unless Instantiatable (framework convention)
- PHP-ALIAS-01 (FieldAliasingRule): Prevents field name shortenings in fetch() and Ajax endpoints (anti-aliasing policy)
All other rules should return false from is_called_during_manifest_scan().
Severity Levels
- Critical: Must fix immediately (e.g., fallback code)
- High: Should fix soon (e.g., mass assignment)
- Medium: Fix when convenient (e.g., naming conventions)
- Low: Minor issues (e.g., filename case)