🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
540 lines
17 KiB
Markdown
Executable File
540 lines
17 KiB
Markdown
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
|
|
|
|
1. **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
|
|
|
|
2. **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
|
|
|
|
3. **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/`)
|
|
|
|
1. **NamingConventionRule** (PHP-NAMING-01)
|
|
- Enforces underscore_case for methods and variables
|
|
- Excludes Laravel framework methods (toArray, firstOrCreate, etc.)
|
|
- Severity: Medium
|
|
|
|
2. **MassAssignmentRule** (PHP-MASS-01)
|
|
- Prohibits use of $fillable property
|
|
- Ensures $guarded = ['*'] or removal
|
|
- Severity: High
|
|
|
|
3. **PhpFallbackLegacyRule** (PHP-FALLBACK-01)
|
|
- Detects "fallback" or "legacy" in comments/function names
|
|
- Enforces fail-loud principle
|
|
- Severity: Critical
|
|
|
|
4. **DbTableUsageRule** (PHP-DB-01)
|
|
- Prohibits DB::table() usage
|
|
- Requires ORM models for database access
|
|
- Severity: High
|
|
|
|
5. **FunctionExistsRule** (PHP-FUNC-01)
|
|
- Prohibits function_exists() checks
|
|
- Enforces predictable runtime environment
|
|
- Severity: High
|
|
|
|
### Jqhtml Rules (`Rules/Jqhtml/`)
|
|
|
|
1. **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/`)
|
|
|
|
1. **VarUsageRule** (JS-VAR-01)
|
|
- Prohibits 'var' keyword, requires let/const
|
|
- Severity: Medium
|
|
|
|
2. **DefensiveCodingRule** (JS-DEFENSIVE-01)
|
|
- Prohibits typeof checks for core classes
|
|
- Core classes always exist in runtime
|
|
- Severity: High
|
|
|
|
3. **InstanceMethodsRule** (JS-STATIC-01)
|
|
- Enforces static methods in JavaScript classes
|
|
- Exceptions allowed with @instance-class comment
|
|
- Severity: Medium
|
|
|
|
4. **JQueryUsageRule** (JS-JQUERY-01)
|
|
- Enforces $ over jQuery
|
|
- Detects deprecated methods (live, die, bind, etc.)
|
|
- Severity: Medium
|
|
|
|
5. **ThisUsageRule** (JS-THIS-01)
|
|
- Detects problematic 'this' usage
|
|
- Suggests class reference pattern
|
|
- Severity: Medium
|
|
|
|
6. **DocumentReadyRule** (JS-READY-01)
|
|
- Prohibits jQuery ready patterns
|
|
- Requires ES6 class with static init()
|
|
- Severity: High
|
|
|
|
7. **JsFallbackLegacyRule** (JS-FALLBACK-01)
|
|
- JavaScript version of fallback/legacy detection
|
|
- Severity: Critical
|
|
|
|
### Common Rules (`Rules/Common/`)
|
|
|
|
1. **FilenameCaseRule** (FILE-CASE-01)
|
|
- Enforces lowercase filenames
|
|
- Severity: Low
|
|
|
|
2. **FilenameEnhancedRule** (FILE-NAME-01)
|
|
- Validates controller/model naming conventions
|
|
- Checks file-class name consistency
|
|
- Severity: Medium
|
|
|
|
3. **RootFilesRule** (FILE-ROOT-01)
|
|
- Restricts files in project root
|
|
- Maintains clean project structure
|
|
- Severity: Medium
|
|
|
|
4. **RsxTestFilesRule** (FILE-RSX-01)
|
|
- Prevents test files directly in rsx/
|
|
- Enforces proper test organization
|
|
- Severity: Medium
|
|
|
|
5. **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/`)
|
|
|
|
1. **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
|
|
|
|
2. **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/`)
|
|
|
|
1. **PhpSanityCheckRule** (PHP-SC-001)
|
|
- Complex pattern detection (currently disabled)
|
|
- Detects suspicious code patterns
|
|
- Severity: Critical
|
|
|
|
## Configuration
|
|
|
|
### Config File (`config/rsx.php`)
|
|
|
|
```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:
|
|
|
|
```php
|
|
'disabled_rules' => [
|
|
'PHP-SC-001', // Temporarily disabled
|
|
],
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Command Line
|
|
|
|
```bash
|
|
# 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 `-EXCEPTION` suffix
|
|
- 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.
|
|
|
|
```php
|
|
// 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
|
|
<?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.
|
|
|
|
```php
|
|
/**
|
|
* 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:**
|
|
|
|
1. Open the rule file in `/system/app/RSpade/CodeQuality/Rules/`
|
|
2. Search for `EXCEPTION` in the file
|
|
3. If found, the rule implements exception checking
|
|
4. 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:**
|
|
|
|
```php
|
|
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:**
|
|
|
|
```php
|
|
// 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:**
|
|
|
|
```php
|
|
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:
|
|
|
|
```javascript
|
|
// @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:**
|
|
1. **STOP** - Do not add exception comments
|
|
2. **ANALYZE** - Determine if the violation is:
|
|
- Invalid defensive coding (should be removed)
|
|
- Valid duck typing (needs exception)
|
|
- External API constraint (needs exception)
|
|
3. **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"
|
|
4. **WAIT** - Wait for programmer to decide
|
|
5. **NEVER** add `@RULE-ID-EXCEPTION` comments 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
|
|
|
|
1. Create a new class extending `CodeQualityRule_Abstract`
|
|
2. Place in appropriate Rules subdirectory
|
|
3. Implement required methods:
|
|
- `get_id()` - Unique rule identifier
|
|
- `get_name()` - Human-readable name
|
|
- `check()` - Violation detection logic
|
|
4. Add to Manifest scan directories if needed
|
|
|
|
Example:
|
|
|
|
```php
|
|
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
|
|
|
|
1. Create a temporary test file with violations
|
|
2. Run `php artisan rsx:check`
|
|
3. Verify violations are detected correctly
|
|
4. Clean up test files
|
|
|
|
## Migration from Monolith
|
|
|
|
The original 1921-line `CodeStandardsChecker.php` has been:
|
|
1. Archived to `/archived/CodeStandardsChecker.old.php`
|
|
2. Split into modular rule classes
|
|
3. Enhanced with auto-discovery via Manifest
|
|
4. 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:
|
|
|
|
1. Enforce critical framework conventions that would break the application
|
|
2. Need to provide immediate feedback before code execution
|
|
3. 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()`:
|
|
|
|
```php
|
|
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:
|
|
|
|
```php
|
|
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)
|