Working state snapshot with debug settings and pending changes
Fix manifest helper delegator missing return statements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
146
app/RSpade/CodeQuality/Rules/Scss/DataSidSelector_CodeQualityRule.php
Executable file
146
app/RSpade/CodeQuality/Rules/Scss/DataSidSelector_CodeQualityRule.php
Executable file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\RSpade\CodeQuality\Rules\Scss;
|
||||
|
||||
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
|
||||
|
||||
/**
|
||||
* DataSidSelectorRule - Prohibits targeting [data-sid] in SCSS/CSS selectors
|
||||
*
|
||||
* PHILOSOPHY: $sid attributes are internal implementation details of jqhtml components.
|
||||
* They exist for programmatic DOM access via this.$sid('name'), not for styling.
|
||||
* Styling should use semantic BEM child classes instead.
|
||||
*/
|
||||
class DataSidSelector_CodeQualityRule extends CodeQualityRule_Abstract
|
||||
{
|
||||
public function get_id(): string
|
||||
{
|
||||
return 'SCSS-SID-01';
|
||||
}
|
||||
|
||||
public function get_name(): string
|
||||
{
|
||||
return 'No data-sid CSS Selectors';
|
||||
}
|
||||
|
||||
public function get_description(): string
|
||||
{
|
||||
return 'Prohibits targeting [data-sid] attributes in CSS selectors. Use BEM child classes instead.';
|
||||
}
|
||||
|
||||
public function get_file_patterns(): array
|
||||
{
|
||||
return ['*.scss', '*.css'];
|
||||
}
|
||||
|
||||
public function get_default_severity(): string
|
||||
{
|
||||
return 'critical';
|
||||
}
|
||||
|
||||
/**
|
||||
* Run at manifest-time for immediate feedback
|
||||
*/
|
||||
public function is_called_during_manifest_scan(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check SCSS/CSS files for [data-sid] selectors
|
||||
*/
|
||||
public function check(string $file_path, string $contents, array $metadata = []): void
|
||||
{
|
||||
// Skip vendor and node_modules
|
||||
if (str_contains($file_path, '/vendor/') || str_contains($file_path, '/node_modules/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip minified files
|
||||
if (str_ends_with($file_path, '.min.css') || str_ends_with($file_path, '.min.scss')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = explode("\n", $contents);
|
||||
|
||||
foreach ($lines as $line_num => $line) {
|
||||
$actual_line_number = $line_num + 1;
|
||||
$trimmed = trim($line);
|
||||
|
||||
// Skip comments
|
||||
if (str_starts_with($trimmed, '//') || str_starts_with($trimmed, '/*')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for exception comment on same line or previous line
|
||||
if (str_contains($line, '@' . $this->get_id() . '-EXCEPTION')) {
|
||||
continue;
|
||||
}
|
||||
if ($line_num > 0 && str_contains($lines[$line_num - 1], '@' . $this->get_id() . '-EXCEPTION')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for [data-sid= pattern (with or without quotes)
|
||||
if (preg_match('/\[data-sid\s*[=~\|\^\$\*]/', $line, $matches)) {
|
||||
// Extract the selector context for better error message
|
||||
$code_snippet = trim($line);
|
||||
if (strlen($code_snippet) > 80) {
|
||||
$code_snippet = substr($code_snippet, 0, 77) . '...';
|
||||
}
|
||||
|
||||
// Try to extract the sid name for the suggestion
|
||||
$sid_name = '';
|
||||
if (preg_match('/\[data-sid\s*=\s*["\']?([^"\'\]]+)/', $line, $sid_match)) {
|
||||
$sid_name = $sid_match[1];
|
||||
}
|
||||
|
||||
$suggestion = $this->get_suggestion($sid_name);
|
||||
|
||||
$this->add_violation(
|
||||
$file_path,
|
||||
$actual_line_number,
|
||||
"Targeting [data-sid] in CSS is PROHIBITED",
|
||||
$code_snippet,
|
||||
$suggestion,
|
||||
'critical'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the fix suggestion with example
|
||||
*/
|
||||
private function get_suggestion(string $sid_name): string
|
||||
{
|
||||
$example_class = $sid_name ? "__$sid_name" : '__element_name';
|
||||
|
||||
return <<<SUGGESTION
|
||||
\$sid attributes are internal implementation details of jqhtml components.
|
||||
They exist for programmatic DOM access (this.\$sid('name')), NOT for styling.
|
||||
|
||||
WHY THIS IS WRONG:
|
||||
- \$sid is an internal identifier, not a semantic class name
|
||||
- Component internals may change without notice
|
||||
- Breaks encapsulation - styles reach into component implementation
|
||||
|
||||
SOLUTION: Use BEM child classes instead.
|
||||
|
||||
Before (WRONG):
|
||||
[data-sid="$sid_name"] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
After (CORRECT):
|
||||
&$example_class {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
Then in the jqhtml template, add the class to the element:
|
||||
<button \$sid="$sid_name" class="Component_Name$example_class">
|
||||
|
||||
BEM child classes are semantic, documented, and part of the component's public API.
|
||||
\$sid attributes are internal implementation that should never leak into CSS.
|
||||
SUGGESTION;
|
||||
}
|
||||
}
|
||||
@@ -535,7 +535,8 @@ class BundleCompiler
|
||||
}
|
||||
|
||||
// Handle class name includes (could be Asset Bundles with CDN assets)
|
||||
if (is_string($include) && Manifest::php_find_class($include)) {
|
||||
// Check manifest data directly since php_find_class throws if not found
|
||||
if (is_string($include) && isset(Manifest::$data['data']['php_classes'][$include])) {
|
||||
if (Manifest::php_is_subclass_of($include, 'Rsx_Asset_Bundle_Abstract')) {
|
||||
$asset_def = $include::define();
|
||||
if (!empty($asset_def['cdn_assets'])) {
|
||||
|
||||
@@ -189,9 +189,9 @@ class Manifest
|
||||
|
||||
public static $_manifest_compile_lock;
|
||||
|
||||
public static $_get_rsx_files_cache;
|
||||
public static $_get_rsx_files_cache = null;
|
||||
|
||||
public static bool $_has_changed_cache;
|
||||
public static array $_has_changed_cache = [];
|
||||
|
||||
public static bool $_has_manifest_ready = false;
|
||||
|
||||
@@ -1209,7 +1209,7 @@ class Manifest
|
||||
// move to lower soon
|
||||
public static function _validate_cached_data()
|
||||
{
|
||||
_Manifest_Cache_Helper::_validate_cached_data();
|
||||
return _Manifest_Cache_Helper::_validate_cached_data();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1258,7 +1258,7 @@ class Manifest
|
||||
*/
|
||||
public static function _collate_files_by_classes()
|
||||
{
|
||||
_Manifest_Builder_Helper::_collate_files_by_classes();
|
||||
return _Manifest_Builder_Helper::_collate_files_by_classes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1281,7 +1281,7 @@ class Manifest
|
||||
*/
|
||||
public static function _build_event_handler_index()
|
||||
{
|
||||
_Manifest_Builder_Helper::_build_event_handler_index();
|
||||
return _Manifest_Builder_Helper::_build_event_handler_index();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1295,7 +1295,7 @@ class Manifest
|
||||
*/
|
||||
public static function _build_classless_php_files_index()
|
||||
{
|
||||
_Manifest_Builder_Helper::_build_classless_php_files_index();
|
||||
return _Manifest_Builder_Helper::_build_classless_php_files_index();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -153,8 +153,6 @@ class _Manifest_Cache_Helper
|
||||
// If cache exists, check if anything changed
|
||||
$files = Manifest::_get_rsx_files();
|
||||
|
||||
$any_changes = false;
|
||||
|
||||
// Check for changed files
|
||||
foreach ($files as $file) {
|
||||
if (Manifest::_has_changed($file)) {
|
||||
@@ -163,23 +161,21 @@ class _Manifest_Cache_Helper
|
||||
}
|
||||
|
||||
// Check for deleted files
|
||||
if (!$any_changes) {
|
||||
$existing_files = array_flip($files);
|
||||
$existing_files = array_flip($files);
|
||||
|
||||
foreach (array_keys(Manifest::$data['data']['files']) as $cached_file) {
|
||||
// Skip storage files - they're not part of the manifest
|
||||
if (str_starts_with($cached_file, 'storage/')) {
|
||||
continue;
|
||||
foreach (array_keys(Manifest::$data['data']['files']) as $cached_file) {
|
||||
// Skip storage files - they're not part of the manifest
|
||||
if (str_starts_with($cached_file, 'storage/')) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($existing_files[$cached_file])) {
|
||||
// Only show the message once per page load
|
||||
if (!Manifest::$__shown_rescan_message) {
|
||||
console_debug('MANIFEST', '* Deleted file ' . $cached_file . ' is triggering manifest rescan *');
|
||||
Manifest::$__shown_rescan_message = true;
|
||||
}
|
||||
if (!isset($existing_files[$cached_file])) {
|
||||
// Only show the message once per page load
|
||||
if (!self::$__shown_rescan_message) {
|
||||
console_debug('MANIFEST', '* Deleted file ' . $cached_file . ' is triggering manifest rescan *');
|
||||
self::$__shown_rescan_message = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,15 @@ use App\RSpade\Core\Manifest\Manifest;
|
||||
*/
|
||||
class _Manifest_Scanner_Helper
|
||||
{
|
||||
protected static $_get_rsx_files_cache;
|
||||
protected static $_has_changed_cache = [];
|
||||
protected static $__shown_rescan_message = false;
|
||||
// Static properties are defined on Manifest class and accessed via Manifest::$property
|
||||
|
||||
/**
|
||||
* Get all files in configured scan directories (returns relative paths)
|
||||
*/
|
||||
public static function _get_rsx_files(): array
|
||||
{
|
||||
if (!empty(self::$_get_rsx_files_cache)) {
|
||||
return self::$_get_rsx_files_cache;
|
||||
if (!empty(Manifest::$_get_rsx_files_cache)) {
|
||||
return Manifest::$_get_rsx_files_cache;
|
||||
}
|
||||
|
||||
$base_path = base_path();
|
||||
@@ -128,7 +126,7 @@ class _Manifest_Scanner_Helper
|
||||
$files = array_unique($files);
|
||||
sort($files);
|
||||
|
||||
self::$_get_rsx_files_cache = $files;
|
||||
Manifest::$_get_rsx_files_cache = $files;
|
||||
|
||||
return $files;
|
||||
}
|
||||
@@ -138,17 +136,17 @@ class _Manifest_Scanner_Helper
|
||||
*/
|
||||
public static function _has_changed(string $file): bool
|
||||
{
|
||||
if (isset(self::$_has_changed_cache[$file])) {
|
||||
return self::$_has_changed_cache[$file];
|
||||
if (isset(Manifest::$_has_changed_cache[$file])) {
|
||||
return Manifest::$_has_changed_cache[$file];
|
||||
}
|
||||
|
||||
if (!isset(Manifest::$data['data']['files'][$file])) {
|
||||
// Only show the message once per page load
|
||||
if (!self::$__shown_rescan_message) {
|
||||
if (!Manifest::$__shown_rescan_message) {
|
||||
console_debug('MANIFEST', '* New file ' . $file . ' is triggering manifest rescan *');
|
||||
self::$__shown_rescan_message = true;
|
||||
Manifest::$__shown_rescan_message = true;
|
||||
}
|
||||
$_has_changed_cache[$file] = true;
|
||||
Manifest::$_has_changed_cache[$file] = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -159,11 +157,11 @@ class _Manifest_Scanner_Helper
|
||||
// Make sure file exists
|
||||
if (!file_exists($absolute_path)) {
|
||||
// Only show the message once per page load
|
||||
if (!self::$__shown_rescan_message) {
|
||||
if (!Manifest::$__shown_rescan_message) {
|
||||
console_debug('MANIFEST', '* File ' . $file . ' appears to be deleted in ' . $absolute_path . ', triggering manifest rescan *');
|
||||
self::$__shown_rescan_message = true;
|
||||
Manifest::$__shown_rescan_message = true;
|
||||
}
|
||||
$_has_changed_cache[$file] = true;
|
||||
Manifest::$_has_changed_cache[$file] = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -173,11 +171,11 @@ class _Manifest_Scanner_Helper
|
||||
// Stage 1: Size check
|
||||
if ($old['size'] != $current_size) {
|
||||
// Only show the message once per page load
|
||||
if (!self::$__shown_rescan_message) {
|
||||
if (!Manifest::$__shown_rescan_message) {
|
||||
console_debug('MANIFEST', '* File ' . $file . ' has changed size, triggering manifest rescan *');
|
||||
self::$__shown_rescan_message = true;
|
||||
Manifest::$__shown_rescan_message = true;
|
||||
}
|
||||
$_has_changed_cache[$file] = true;
|
||||
Manifest::$_has_changed_cache[$file] = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -186,16 +184,16 @@ class _Manifest_Scanner_Helper
|
||||
$current_mtime = filemtime($absolute_path);
|
||||
if ($old['mtime'] != $current_mtime) {
|
||||
// Only show the message once per page load
|
||||
if (!self::$__shown_rescan_message) {
|
||||
if (!Manifest::$__shown_rescan_message) {
|
||||
console_debug('MANIFEST', '* File ' . $file . ' has changed mtime, triggering manifest rescan *');
|
||||
self::$__shown_rescan_message = true;
|
||||
Manifest::$__shown_rescan_message = true;
|
||||
}
|
||||
$_has_changed_cache[$file] = true;
|
||||
Manifest::$_has_changed_cache[$file] = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$_has_changed_cache[$file] = false;
|
||||
Manifest::$_has_changed_cache[$file] = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ class Rsx_Framework_Provider extends ServiceProvider
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
// Sanity check: .env symlink configuration
|
||||
// The system/.env must be a symlink to ../.env for proper configuration sharing
|
||||
$this->check_env_symlink();
|
||||
|
||||
// Merge framework config defaults
|
||||
$package_config = base_path('config/rsx.php');
|
||||
$this->mergeConfigFrom($package_config, 'rsx');
|
||||
@@ -506,4 +510,74 @@ class Rsx_Framework_Provider extends ServiceProvider
|
||||
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that .env is properly symlinked
|
||||
*
|
||||
* The system/.env file must be a symlink to ../.env for proper configuration sharing.
|
||||
* This ensures both the project root and Laravel see the same .env file.
|
||||
*/
|
||||
private function check_env_symlink(): void
|
||||
{
|
||||
// Only check if we're in the standard /var/www/html/system location
|
||||
if (base_path() !== '/var/www/html/system') {
|
||||
return;
|
||||
}
|
||||
|
||||
$project_env = '/var/www/html/.env';
|
||||
$system_env = '/var/www/html/system/.env';
|
||||
|
||||
// Both files must exist for this check to apply
|
||||
if (!file_exists($project_env) || !file_exists($system_env)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// system/.env must be a symlink
|
||||
if (!is_link($system_env)) {
|
||||
$this->fatal_env_symlink_error();
|
||||
}
|
||||
|
||||
// The symlink must point to ../.env (relative path)
|
||||
$link_target = readlink($system_env);
|
||||
if ($link_target !== '../.env') {
|
||||
$this->fatal_env_symlink_error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display fatal error for .env symlink misconfiguration
|
||||
*/
|
||||
private function fatal_env_symlink_error(): void
|
||||
{
|
||||
$message = <<<'ERROR'
|
||||
|
||||
================================================================================
|
||||
FATAL ERROR: .env Symlink Misconfiguration
|
||||
================================================================================
|
||||
|
||||
The file /var/www/html/system/.env must be a symlink to ../.env
|
||||
|
||||
This ensures both the project root and the Laravel framework share the same
|
||||
environment configuration. Without this symlink, configuration changes in one
|
||||
location won't be reflected in the other.
|
||||
|
||||
TO FIX THIS ISSUE:
|
||||
|
||||
cd /var/www/html/system
|
||||
rm .env
|
||||
ln -s ../.env .env
|
||||
|
||||
After creating the symlink, your .env setup should look like:
|
||||
|
||||
/var/www/html/.env (the actual .env file)
|
||||
/var/www/html/system/.env -> ../.env (symlink)
|
||||
|
||||
================================================================================
|
||||
|
||||
ERROR;
|
||||
|
||||
// Use echo + die for maximum visibility (before any error handlers are set up)
|
||||
echo $message;
|
||||
die(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user