Add incremental manifest-time code quality checks and JS duplicate method detection
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -480,6 +480,46 @@ By default, code quality rules run only when `php artisan rsx:check` is executed
|
|||||||
2. Need to provide immediate feedback before code execution
|
2. Need to provide immediate feedback before code execution
|
||||||
3. Have been specifically requested by framework maintainers
|
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
|
### Current Manifest-Time Rules
|
||||||
|
|
||||||
Only the following rules are approved for manifest-time execution:
|
Only the following rules are approved for manifest-time execution:
|
||||||
|
|||||||
@@ -92,6 +92,28 @@ abstract class CodeQualityRule_Abstract
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this rule checks files incrementally or needs cross-file context
|
||||||
|
*
|
||||||
|
* This method is only relevant for rules where is_called_during_manifest_scan() = true.
|
||||||
|
*
|
||||||
|
* INCREMENTAL (true - default):
|
||||||
|
* - Rule checks each file independently
|
||||||
|
* - Only changed files are passed during incremental manifest rebuilds
|
||||||
|
* - More efficient for per-file validation rules
|
||||||
|
*
|
||||||
|
* CROSS-FILE (false):
|
||||||
|
* - Rule needs to see relationships between files or check the full manifest
|
||||||
|
* - Runs once per manifest build with access to all files via Manifest::get_all()
|
||||||
|
* - Use for rules that validate naming across files, check for duplicates, etc.
|
||||||
|
*
|
||||||
|
* @return bool True for per-file rules, false for cross-file rules
|
||||||
|
*/
|
||||||
|
public function is_incremental(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default severity for this rule
|
* Get default severity for this rule
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ class DecoratorIdentifierParam_CodeQualityRule extends CodeQualityRule_Abstract
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cross-file rule - needs full manifest context
|
||||||
|
*/
|
||||||
|
public function is_incremental(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the manifest for decorator identifier parameter violations
|
* Check the manifest for decorator identifier parameter violations
|
||||||
*/
|
*/
|
||||||
|
|||||||
142
app/RSpade/CodeQuality/Rules/JavaScript/JsDuplicateMethod_CodeQualityRule.php
Executable file
142
app/RSpade/CodeQuality/Rules/JavaScript/JsDuplicateMethod_CodeQualityRule.php
Executable file
@@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\CodeQuality\Rules\JavaScript;
|
||||||
|
|
||||||
|
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsDuplicateMethodRule - Detects duplicate method definitions in ES6 classes
|
||||||
|
*
|
||||||
|
* JavaScript allows defining the same method name multiple times in a class,
|
||||||
|
* but later definitions silently overwrite earlier ones. This is almost always
|
||||||
|
* a mistake - commonly caused by copy/paste errors or merge conflicts.
|
||||||
|
*
|
||||||
|
* Example of problematic code:
|
||||||
|
* class MyComponent extends Component {
|
||||||
|
* on_render() { ... } // First definition
|
||||||
|
* // ... lots of code ...
|
||||||
|
* on_render() { ... } // Overwrites the first one silently!
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class JsDuplicateMethod_CodeQualityRule extends CodeQualityRule_Abstract
|
||||||
|
{
|
||||||
|
public function get_id(): string
|
||||||
|
{
|
||||||
|
return 'JS-DUPLICATE-METHOD-01';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_name(): string
|
||||||
|
{
|
||||||
|
return 'Duplicate Method Definition';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_description(): string
|
||||||
|
{
|
||||||
|
return 'Detects ES6 class methods that are defined more than once (later definitions overwrite earlier ones)';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_file_patterns(): array
|
||||||
|
{
|
||||||
|
return ['*.js'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_default_severity(): string
|
||||||
|
{
|
||||||
|
return 'critical';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This rule runs during manifest scan for immediate feedback
|
||||||
|
*/
|
||||||
|
public function is_called_during_manifest_scan(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check JavaScript file for duplicate method definitions
|
||||||
|
*/
|
||||||
|
public function check(string $file_path, string $contents, array $metadata = []): void
|
||||||
|
{
|
||||||
|
// Skip if no duplicate methods detected by parser
|
||||||
|
if (empty($metadata['duplicate_methods'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$class_name = $metadata['class'] ?? 'unknown';
|
||||||
|
|
||||||
|
foreach ($metadata['duplicate_methods'] as $duplicate) {
|
||||||
|
$method_name = $duplicate['name'];
|
||||||
|
$is_static = $duplicate['static'] ?? false;
|
||||||
|
$first_line = $duplicate['firstLine'] ?? 1;
|
||||||
|
$second_line = $duplicate['secondLine'] ?? 1;
|
||||||
|
|
||||||
|
$static_prefix = $is_static ? 'static ' : '';
|
||||||
|
$method_type = $is_static ? 'Static method' : 'Method';
|
||||||
|
|
||||||
|
$this->add_violation(
|
||||||
|
$file_path,
|
||||||
|
$second_line,
|
||||||
|
"{$method_type} '{$method_name}' is defined multiple times in class {$class_name}",
|
||||||
|
$this->extract_code_lines($contents, $first_line, $second_line),
|
||||||
|
$this->get_remediation($method_name, $class_name, $is_static, $first_line, $second_line),
|
||||||
|
'critical'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract code snippets for both method definitions
|
||||||
|
*/
|
||||||
|
private function extract_code_lines(string $contents, int $first_line, int $second_line): string
|
||||||
|
{
|
||||||
|
$lines = explode("\n", $contents);
|
||||||
|
$snippets = [];
|
||||||
|
|
||||||
|
if (isset($lines[$first_line - 1])) {
|
||||||
|
$snippets[] = "Line {$first_line}: " . trim($lines[$first_line - 1]);
|
||||||
|
}
|
||||||
|
if (isset($lines[$second_line - 1])) {
|
||||||
|
$snippets[] = "Line {$second_line}: " . trim($lines[$second_line - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode("\n", $snippets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get remediation instructions
|
||||||
|
*/
|
||||||
|
private function get_remediation(string $method_name, string $class_name, bool $is_static, int $first_line, int $second_line): string
|
||||||
|
{
|
||||||
|
$static_prefix = $is_static ? 'static ' : '';
|
||||||
|
|
||||||
|
return "DUPLICATE METHOD DEFINITION: '{$method_name}' is defined twice in {$class_name}
|
||||||
|
|
||||||
|
PROBLEM:
|
||||||
|
JavaScript allows defining the same method name multiple times in a class,
|
||||||
|
but later definitions silently overwrite earlier ones. This means:
|
||||||
|
- The first definition at line {$first_line} is IGNORED
|
||||||
|
- Only the second definition at line {$second_line} will be executed
|
||||||
|
|
||||||
|
This is almost always a mistake caused by:
|
||||||
|
- Copy/paste errors
|
||||||
|
- Merge conflicts
|
||||||
|
- Forgetting an earlier implementation exists
|
||||||
|
|
||||||
|
SOLUTION:
|
||||||
|
1. Review both definitions to understand what each was intended to do
|
||||||
|
2. Merge the logic if both are needed, OR
|
||||||
|
3. Remove the duplicate definition if one is obsolete
|
||||||
|
|
||||||
|
FIRST DEFINITION (line {$first_line}) - THIS IS BEING IGNORED:
|
||||||
|
Look at what this implementation does - is any of this logic needed?
|
||||||
|
|
||||||
|
SECOND DEFINITION (line {$second_line}) - THIS IS THE ACTIVE ONE:
|
||||||
|
This is the only version that will actually run.
|
||||||
|
|
||||||
|
COMMON PATTERNS:
|
||||||
|
- If both have different logic: merge them into a single method
|
||||||
|
- If they're identical: remove one (preferably keep the better-documented one)
|
||||||
|
- If one is outdated: remove the obsolete version";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,14 @@ class InstanceMethods_CodeQualityRule extends CodeQualityRule_Abstract
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cross-file rule - needs full manifest context
|
||||||
|
*/
|
||||||
|
public function is_incremental(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the manifest for instance method violations
|
* Check the manifest for instance method violations
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ class Monoprogenic_CodeQualityRule extends CodeQualityRule_Abstract
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cross-file rule - needs full manifest context
|
||||||
|
*/
|
||||||
|
public function is_incremental(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the manifest for Monoprogenic violations
|
* Check the manifest for Monoprogenic violations
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ class RsxControllerInheritance_CodeQualityRule extends CodeQualityRule_Abstract
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cross-file rule - needs full manifest context
|
||||||
|
*/
|
||||||
|
public function is_incremental(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for RSX controllers extending Laravel's Controller
|
* Check for RSX controllers extending Laravel's Controller
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ class ScssClassScope_CodeQualityRule extends CodeQualityRule_Abstract
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cross-file rule - needs full manifest context
|
||||||
|
*/
|
||||||
|
public function is_incremental(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default severity for this rule
|
* Get default severity for this rule
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ class SpaAttributeMisuse_CodeQualityRule extends CodeQualityRule_Abstract
|
|||||||
return 'critical';
|
return 'critical';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cross-file rule - needs full manifest context
|
||||||
|
*/
|
||||||
|
public function is_incremental(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the manifest for SPA + Route attribute combinations
|
* Check the manifest for SPA + Route attribute combinations
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -50,9 +50,14 @@ class Ajax {
|
|||||||
const error = event.reason;
|
const error = event.reason;
|
||||||
console.error('Uncaught Ajax error:', error);
|
console.error('Uncaught Ajax error:', error);
|
||||||
|
|
||||||
// Show Modal.error() for uncaught Ajax errors
|
// Show error modal for uncaught Ajax errors
|
||||||
if (typeof Modal !== 'undefined' && Modal.error) {
|
// In debug mode, use fatal_error() for detailed file/line info
|
||||||
await Modal.error(error, 'Uncaught Ajax Error');
|
if (typeof Modal !== 'undefined') {
|
||||||
|
if (window.rsxapp?.debug && Modal.fatal_error) {
|
||||||
|
await Modal.fatal_error(error, 'Uncaught Ajax Error');
|
||||||
|
} else if (Modal.error) {
|
||||||
|
await Modal.error(error, 'Uncaught Ajax Error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,17 +26,28 @@ function sleep(milliseconds = 0) {
|
|||||||
/**
|
/**
|
||||||
* Creates a debounced function with exclusivity and promise fan-in
|
* Creates a debounced function with exclusivity and promise fan-in
|
||||||
*
|
*
|
||||||
* This function, when invoked, immediately runs the callback exclusively.
|
* BEHAVIORAL GUARANTEES:
|
||||||
* For subsequent invocations, it applies a delay before running the callback exclusively again.
|
|
||||||
* The delay starts after the current asynchronous operation resolves.
|
|
||||||
*
|
*
|
||||||
* If 'delay' is set to 0, the function will only prevent enqueueing multiple executions of the
|
* 1. IMMEDIATE START: The first call (or any call when idle and delay has passed) executes
|
||||||
* same method more than once, but will still run them immediately in an exclusive sequential manner.
|
* immediately without waiting.
|
||||||
*
|
*
|
||||||
* The most recent invocation of the function will be the parameters that get passed to the function
|
* 2. AWAITS COMPLETION: The callback is awaited - async operations (network requests, etc.)
|
||||||
* when it invokes.
|
* complete before the execution is considered finished.
|
||||||
*
|
*
|
||||||
* The function returns a promise that resolves when the next exclusive execution completes.
|
* 3. POST-EXECUTION DELAY: After callback completion, no further executions occur for at least
|
||||||
|
* `delay` milliseconds. The delay timer starts AFTER the callback resolves/rejects.
|
||||||
|
*
|
||||||
|
* 4. COALESCED QUEUING: If called during execution OR during the post-execution delay, requests
|
||||||
|
* are coalesced into ONE pending execution using the LAST provided arguments. When the delay
|
||||||
|
* expires, exactly one execution occurs with those final arguments.
|
||||||
|
*
|
||||||
|
* 5. GUARANTEED EXECUTION: Any call made during an active execution is guaranteed to trigger
|
||||||
|
* a subsequent execution (with that call's args or later args if more calls arrive).
|
||||||
|
*
|
||||||
|
* 6. PROMISE FAN-IN: All callers waiting for the same execution receive the same result/error.
|
||||||
|
*
|
||||||
|
* If 'delay' is set to 0, the function still enforces exclusivity (no parallel execution) but
|
||||||
|
* queued executions run immediately after the current one completes.
|
||||||
*
|
*
|
||||||
* Usage as function:
|
* Usage as function:
|
||||||
* const debouncedFn = debounce(myFunction, 250);
|
* const debouncedFn = debounce(myFunction, 250);
|
||||||
@@ -46,9 +57,9 @@ function sleep(milliseconds = 0) {
|
|||||||
* myMethod() { ... }
|
* myMethod() { ... }
|
||||||
*
|
*
|
||||||
* @param {function|number} callback_or_delay The callback function OR delay when used as decorator
|
* @param {function|number} callback_or_delay The callback function OR delay when used as decorator
|
||||||
* @param {number} delay The delay in milliseconds before subsequent invocations
|
* @param {number} delay The delay in milliseconds after completion before next execution
|
||||||
* @param {boolean} immediate if true, the first time the action is called, the callback executes immediately
|
* @param {boolean} immediate Unused - first call always runs immediately regardless of this value
|
||||||
* @returns {function} A function that when invoked, runs the callback immediately and exclusively,
|
* @returns {function} A debounced function that returns a Promise resolving to the callback result
|
||||||
*
|
*
|
||||||
* @decorator
|
* @decorator
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -505,6 +505,11 @@ JS;
|
|||||||
if (!empty($method_decorators)) {
|
if (!empty($method_decorators)) {
|
||||||
$data['method_decorators'] = $method_decorators;
|
$data['method_decorators'] = $method_decorators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store duplicate methods if any found (methods defined more than once)
|
||||||
|
if (!empty($first_class['duplicateMethods'])) {
|
||||||
|
$data['duplicate_methods'] = $first_class['duplicateMethods'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($parsed['imports'])) {
|
if (!empty($parsed['imports'])) {
|
||||||
|
|||||||
@@ -362,9 +362,13 @@ function parseFileContent(content, filePath, jsonOutput) {
|
|||||||
public_static_methods: {},
|
public_static_methods: {},
|
||||||
properties: {},
|
properties: {},
|
||||||
staticProperties: {},
|
staticProperties: {},
|
||||||
decorators: allDecorators
|
decorators: allDecorators,
|
||||||
|
duplicateMethods: [] // Track methods defined more than once
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Track seen method names to detect duplicates
|
||||||
|
const seenMethods = {}; // methodName -> { static: bool, line: number }
|
||||||
|
|
||||||
// Extract methods and properties
|
// Extract methods and properties
|
||||||
path.node.body.body.forEach(member => {
|
path.node.body.body.forEach(member => {
|
||||||
if (t.isClassMethod(member)) {
|
if (t.isClassMethod(member)) {
|
||||||
@@ -380,6 +384,21 @@ function parseFileContent(content, filePath, jsonOutput) {
|
|||||||
// Determine visibility: private if starts with #, otherwise public
|
// Determine visibility: private if starts with #, otherwise public
|
||||||
const methodName = member.key.name || (t.isPrivateName(member.key) ? '#' + member.key.id.name : 'unknown');
|
const methodName = member.key.name || (t.isPrivateName(member.key) ? '#' + member.key.id.name : 'unknown');
|
||||||
const visibility = methodName.startsWith('#') ? 'private' : 'public';
|
const visibility = methodName.startsWith('#') ? 'private' : 'public';
|
||||||
|
const methodLine = member.loc ? member.loc.start.line : null;
|
||||||
|
|
||||||
|
// Check for duplicate method definition
|
||||||
|
const methodKey = (member.static ? 'static:' : 'instance:') + methodName;
|
||||||
|
if (seenMethods[methodKey]) {
|
||||||
|
// This is a duplicate!
|
||||||
|
classInfo.duplicateMethods.push({
|
||||||
|
name: methodName,
|
||||||
|
static: member.static,
|
||||||
|
firstLine: seenMethods[methodKey].line,
|
||||||
|
secondLine: methodLine
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
seenMethods[methodKey] = { line: methodLine };
|
||||||
|
}
|
||||||
|
|
||||||
const methodInfo = {
|
const methodInfo = {
|
||||||
// PHP-compatible fields
|
// PHP-compatible fields
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ private static $_manifest_loaded_at = null; // Load timestamp
|
|||||||
- `get_all()` - Returns all files array
|
- `get_all()` - Returns all files array
|
||||||
- `get_file($path)` - Get specific file metadata (throws if not found)
|
- `get_file($path)` - Get specific file metadata (throws if not found)
|
||||||
- `get_files_by_dir($dir)` - Get all files in directory
|
- `get_files_by_dir($dir)` - Get all files in directory
|
||||||
|
- `get_changed_files()` - Get files that changed in most recent manifest scan (for incremental code quality checks)
|
||||||
|
|
||||||
#### PHP Class Resolution
|
#### PHP Class Resolution
|
||||||
- `php_find_class($name)` - Find by simple class name
|
- `php_find_class($name)` - Find by simple class name
|
||||||
@@ -284,6 +285,15 @@ Register in `config/rsx.php`:
|
|||||||
|
|
||||||
The manifest integrates with code quality checks via metadata storage. Rules that run during manifest scan store violations in `code_quality_metadata` field for each file.
|
The manifest integrates with code quality checks via metadata storage. Rules that run during manifest scan store violations in `code_quality_metadata` field for each file.
|
||||||
|
|
||||||
|
### Incremental Code Quality Checks
|
||||||
|
|
||||||
|
Manifest-time code quality rules support incremental checking for better performance:
|
||||||
|
|
||||||
|
- **Incremental rules** (`is_incremental() = true`): Only check files that changed in this manifest scan
|
||||||
|
- **Cross-file rules** (`is_incremental() = false`): Run once per build, access full manifest via `get_all()`
|
||||||
|
|
||||||
|
The `get_changed_files()` API provides the list of files that changed, enabling rules to optimize their processing.
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
Enable verbose output:
|
Enable verbose output:
|
||||||
|
|||||||
@@ -191,6 +191,9 @@ class Manifest
|
|||||||
// Track if we've already shown the manifest rescan message this page load
|
// Track if we've already shown the manifest rescan message this page load
|
||||||
protected static bool $__shown_rescan_message = false;
|
protected static bool $__shown_rescan_message = false;
|
||||||
|
|
||||||
|
// Files that changed in the most recent manifest scan (for incremental code quality checks)
|
||||||
|
protected static array $_changed_files = [];
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// Query Methods
|
// Query Methods
|
||||||
// ========================================
|
// ========================================
|
||||||
@@ -216,6 +219,19 @@ class Manifest
|
|||||||
return static::$data['data']['autoloader_class_map'] ?? [];
|
return static::$data['data']['autoloader_class_map'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of files that changed in the most recent manifest scan
|
||||||
|
*
|
||||||
|
* Used by code quality rules that need to know which files changed for
|
||||||
|
* incremental processing. Returns empty array if manifest was fully rebuilt.
|
||||||
|
*
|
||||||
|
* @return array Array of relative file paths that changed
|
||||||
|
*/
|
||||||
|
public static function get_changed_files(): array
|
||||||
|
{
|
||||||
|
return static::$_changed_files;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get data for a specific file
|
* Get data for a specific file
|
||||||
*/
|
*/
|
||||||
@@ -1303,6 +1319,9 @@ class Manifest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store changed files for incremental code quality checks
|
||||||
|
static::$_changed_files = $files_to_process;
|
||||||
|
|
||||||
console_debug('MANIFEST', 'Phase 1: File Discovery - ' . count($files) . ' files, ' . count($files_to_process) . ' changed');
|
console_debug('MANIFEST', 'Phase 1: File Discovery - ' . count($files) . ' files, ' . count($files_to_process) . ' changed');
|
||||||
|
|
||||||
// If any files have changed and we're not in production, run auto-reformat
|
// If any files have changed and we're not in production, run auto-reformat
|
||||||
@@ -1518,7 +1537,7 @@ class Manifest
|
|||||||
// Skip during migrations - database may not be provisioned yet
|
// Skip during migrations - database may not be provisioned yet
|
||||||
if (env('APP_ENV') !== 'production' && $changes && !static::__is_migration_context()) {
|
if (env('APP_ENV') !== 'production' && $changes && !static::__is_migration_context()) {
|
||||||
static::__verify_database_provisioned();
|
static::__verify_database_provisioned();
|
||||||
static::__run_manifest_time_code_quality_checks();
|
static::__run_manifest_time_code_quality_checks($files_to_process);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a file was auto-renamed and manifest needs to restart
|
// Check if a file was auto-renamed and manifest needs to restart
|
||||||
@@ -3661,8 +3680,14 @@ class Manifest
|
|||||||
* Run code quality checks during manifest scan
|
* Run code quality checks during manifest scan
|
||||||
* Only runs in development mode after manifest changes
|
* Only runs in development mode after manifest changes
|
||||||
* Throws fatal exception on first violation found
|
* Throws fatal exception on first violation found
|
||||||
|
*
|
||||||
|
* Supports two types of rules:
|
||||||
|
* - Incremental rules (is_incremental() = true): Only check changed files
|
||||||
|
* - Cross-file rules (is_incremental() = false): Run once with full manifest context
|
||||||
|
*
|
||||||
|
* @param array $changed_files Files that changed in this manifest scan
|
||||||
*/
|
*/
|
||||||
protected static function __run_manifest_time_code_quality_checks(): void
|
protected static function __run_manifest_time_code_quality_checks(array $changed_files = []): void
|
||||||
{
|
{
|
||||||
// Create a minimal violation collector
|
// Create a minimal violation collector
|
||||||
$collector = new \App\RSpade\CodeQuality\Support\ViolationCollector();
|
$collector = new \App\RSpade\CodeQuality\Support\ViolationCollector();
|
||||||
@@ -3675,78 +3700,129 @@ class Manifest
|
|||||||
true // Only get rules with is_called_during_manifest_scan() = true
|
true // Only get rules with is_called_during_manifest_scan() = true
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($rules as $rule) {
|
// Separate rules by type
|
||||||
// Get file patterns this rule applies to
|
$incremental_rules = [];
|
||||||
$patterns = $rule->get_file_patterns();
|
$cross_file_rules = [];
|
||||||
|
|
||||||
// Check each file in the manifest
|
foreach ($rules as $rule) {
|
||||||
foreach (static::$data['data']['files'] as $file_path => $metadata) {
|
if ($rule->is_incremental()) {
|
||||||
// Check if file matches any pattern
|
$incremental_rules[] = $rule;
|
||||||
$matches = false;
|
} else {
|
||||||
foreach ($patterns as $pattern) {
|
$cross_file_rules[] = $rule;
|
||||||
$pattern = str_replace('*', '.*', $pattern);
|
}
|
||||||
if (preg_match('/^' . $pattern . '$/i', basename($file_path))) {
|
}
|
||||||
$matches = true;
|
|
||||||
break;
|
// Helper to throw violation exception
|
||||||
}
|
$throw_violation = function ($collector) {
|
||||||
|
$violations = $collector->get_all();
|
||||||
|
if (!empty($violations)) {
|
||||||
|
$first_violation = $violations[0];
|
||||||
|
$data = $first_violation->to_array();
|
||||||
|
|
||||||
|
static::_set_manifest_is_bad();
|
||||||
|
|
||||||
|
$relative_file = str_replace(base_path() . '/', '', $data['file']);
|
||||||
|
|
||||||
|
$error_message = "Code Quality Violation ({$data['type']}) - {$data['message']}\n\n";
|
||||||
|
$error_message .= "File: {$relative_file}:{$data['line']}\n\n";
|
||||||
|
|
||||||
|
if (!empty($data['code'])) {
|
||||||
|
$error_message .= "Code:\n " . $data['code'] . "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$matches) {
|
if (!empty($data['resolution'])) {
|
||||||
|
$error_message .= "Resolution:\n" . $data['resolution'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \App\RSpade\CodeQuality\RuntimeChecks\YoureDoingItWrongException(
|
||||||
|
$error_message,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
$data['file'],
|
||||||
|
$data['line']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to check if file matches rule patterns
|
||||||
|
$file_matches_rule = function ($file_path, $rule) {
|
||||||
|
$patterns = $rule->get_file_patterns();
|
||||||
|
foreach ($patterns as $pattern) {
|
||||||
|
$pattern = str_replace('*', '.*', $pattern);
|
||||||
|
if (preg_match('/^' . $pattern . '$/i', basename($file_path))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// INCREMENTAL RULES: Only check changed files
|
||||||
|
// =========================================================
|
||||||
|
// Determine which files to check - changed files only for incremental rebuild,
|
||||||
|
// or all files if this is a full rebuild (empty changed_files means check all)
|
||||||
|
$files_to_check = !empty($changed_files)
|
||||||
|
? array_flip($changed_files) // Use as lookup for O(1) checks
|
||||||
|
: null; // null = check all files
|
||||||
|
|
||||||
|
foreach ($incremental_rules as $rule) {
|
||||||
|
foreach (static::$data['data']['files'] as $file_path => $metadata) {
|
||||||
|
// Skip files that haven't changed (for incremental rebuilds)
|
||||||
|
if ($files_to_check !== null && !isset($files_to_check[$file_path])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get absolute path
|
if (!$file_matches_rule($file_path, $rule)) {
|
||||||
$absolute_path = base_path($file_path);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip if file doesn't exist
|
$absolute_path = base_path($file_path);
|
||||||
if (!file_exists($absolute_path)) {
|
if (!file_exists($absolute_path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read file contents
|
|
||||||
$contents = file_get_contents($absolute_path);
|
$contents = file_get_contents($absolute_path);
|
||||||
|
|
||||||
// Run the rule check - include code_quality_metadata if available
|
|
||||||
$enhanced_metadata = $metadata;
|
$enhanced_metadata = $metadata;
|
||||||
if (isset(static::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()])) {
|
if (isset(static::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()])) {
|
||||||
$enhanced_metadata['rule_metadata'] = static::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()];
|
$enhanced_metadata['rule_metadata'] = static::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()];
|
||||||
}
|
}
|
||||||
|
|
||||||
$rule->check($absolute_path, $contents, $enhanced_metadata);
|
$rule->check($absolute_path, $contents, $enhanced_metadata);
|
||||||
|
$throw_violation($collector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if any violations were found - throw on first violation
|
// =========================================================
|
||||||
$violations = $collector->get_all();
|
// CROSS-FILE RULES: Run once with full manifest context
|
||||||
if (!empty($violations)) {
|
// =========================================================
|
||||||
// Get the first violation
|
// These rules internally access Manifest::get_all() to check all files
|
||||||
$first_violation = $violations[0];
|
// We just need to trigger them once with any matching file
|
||||||
$data = $first_violation->to_array();
|
foreach ($cross_file_rules as $rule) {
|
||||||
|
// Find first file matching the rule's patterns to trigger the check
|
||||||
// Mark manifest as bad to force re-checking on next attempt
|
foreach (static::$data['data']['files'] as $file_path => $metadata) {
|
||||||
static::_set_manifest_is_bad();
|
if (!$file_matches_rule($file_path, $rule)) {
|
||||||
|
continue;
|
||||||
// Format file path relative to project root
|
|
||||||
$relative_file = str_replace(base_path() . '/', '', $data['file']);
|
|
||||||
|
|
||||||
// Build error message
|
|
||||||
$error_message = "Code Quality Violation ({$data['type']}) - {$data['message']}\n\n";
|
|
||||||
$error_message .= "File: {$relative_file}:{$data['line']}\n\n";
|
|
||||||
|
|
||||||
if (!empty($data['code'])) {
|
|
||||||
$error_message .= "Code:\n " . $data['code'] . "\n\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($data['resolution'])) {
|
|
||||||
$error_message .= "Resolution:\n" . $data['resolution'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw fatal exception with the first violation, passing file and line for accurate error display
|
|
||||||
throw new \App\RSpade\CodeQuality\RuntimeChecks\YoureDoingItWrongException(
|
|
||||||
$error_message,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
$data['file'],
|
|
||||||
$data['line']
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$absolute_path = base_path($file_path);
|
||||||
|
if (!file_exists($absolute_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($absolute_path);
|
||||||
|
|
||||||
|
$enhanced_metadata = $metadata;
|
||||||
|
if (isset(static::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()])) {
|
||||||
|
$enhanced_metadata['rule_metadata'] = static::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross-file rules run once, they handle iteration internally
|
||||||
|
$rule->check($absolute_path, $contents, $enhanced_metadata);
|
||||||
|
$throw_violation($collector);
|
||||||
|
|
||||||
|
// Only trigger once per cross-file rule
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
node_modules/.package-lock.json
generated
vendored
24
node_modules/.package-lock.json
generated
vendored
@@ -2211,9 +2211,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/core": {
|
"node_modules/@jqhtml/core": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.32.tgz",
|
||||||
"integrity": "sha512-VbTAbFF8QVOljNjf+1OZ4cFTE3NaeNzyMidqrooYSsHZvb6Ja5NIMDft+M4FxeidrMoRIwa7QN09XgiJWBVNRg==",
|
"integrity": "sha512-g2BZLfNI4vGS35eR+qVP3WJHMcvYsun8ZFLMlVOB58CnJfdCMeXLhiXZrGr3OFFoxAPaitOG2rmzwqMXKfCvOQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
@@ -2237,9 +2237,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/parser": {
|
"node_modules/@jqhtml/parser": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.32.tgz",
|
||||||
"integrity": "sha512-ILV1onWn+rMdwaPd6DYIPM0dj2aUExTJ4ww4c0/+h3Zk50gnxMJQc6fOirDrTB1nWwKtY19yFDMPFYnurOJ2wA==",
|
"integrity": "sha512-YCycfJkxFyr8PsWH8o0wJBImcCi6Eq4m1XFlMDXwAYlBre3H/GkeYpe3JN7xdYP5f/T4Q8RolBW2Y0vzywl27g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.11",
|
||||||
@@ -2277,9 +2277,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/ssr": {
|
"node_modules/@jqhtml/ssr": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.32.tgz",
|
||||||
"integrity": "sha512-EpZ597l/3MEgpvAJTqcZ81cVTJwYxJzs8BLXAdbnQY+ySeOYQL8ot31IV3hdS8b6FnYezzaHN+jSBtqZZsAYnQ==",
|
"integrity": "sha512-VFz3rPSdPn8ZZg36Cb8XujALNXXIyTxMdj8kNhNNhcqBWvKn8XxygyZrGCPCpO9cyRJKlhzDpEX+tW4qMqvPng==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
@@ -2373,9 +2373,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/vscode-extension": {
|
"node_modules/@jqhtml/vscode-extension": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.32.tgz",
|
||||||
"integrity": "sha512-aQoKAxLz1ziuJ6I2EfFXxUBvPEsDxirR2q/6VwEzYqfVTHdKiw6M2Sk25Nhm/lrg5dNLuiKa+snRodV8yEOWpQ==",
|
"integrity": "sha512-UL/7JpIi2BS1QLzj60kYg/Womf7O+UHjK0c2/JnSowl813FUmDFgA4DWog1DlUllbUnf4cRvY5MX302FibqGHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.74.0"
|
"vscode": "^1.74.0"
|
||||||
|
|||||||
17
node_modules/@jqhtml/core/dist/component.d.ts
generated
vendored
17
node_modules/@jqhtml/core/dist/component.d.ts
generated
vendored
@@ -353,6 +353,23 @@ export declare class Jqhtml_Component {
|
|||||||
* Used to determine if cleanup logic needs to run
|
* Used to determine if cleanup logic needs to run
|
||||||
*/
|
*/
|
||||||
_on_registered(event_name: string): boolean;
|
_on_registered(event_name: string): boolean;
|
||||||
|
/**
|
||||||
|
* Invalidate a lifecycle event - removes the "already occurred" marker
|
||||||
|
*
|
||||||
|
* This is the opposite of trigger(). After invalidate() is called:
|
||||||
|
* - New .on() handlers will NOT fire immediately
|
||||||
|
* - The ready() promise will NOT resolve immediately
|
||||||
|
* - Handlers wait for the next trigger() call
|
||||||
|
*
|
||||||
|
* Existing registered callbacks are NOT removed - they'll fire on next trigger().
|
||||||
|
*
|
||||||
|
* Use case: Call invalidate('ready') at the start of reload() or render()
|
||||||
|
* so that any new .on('ready') handlers wait for the reload/render to complete
|
||||||
|
* rather than firing immediately based on the previous lifecycle's state.
|
||||||
|
*
|
||||||
|
* @param event_name - Name of the event to invalidate
|
||||||
|
*/
|
||||||
|
invalidate(event_name: string): void;
|
||||||
/**
|
/**
|
||||||
* Find element by scoped ID
|
* Find element by scoped ID
|
||||||
*
|
*
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/component.d.ts.map
generated
vendored
2
node_modules/@jqhtml/core/dist/component.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}
|
||||||
33
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
33
node_modules/@jqhtml/core/dist/index.cjs
generated
vendored
@@ -2684,6 +2684,9 @@ class Jqhtml_Component {
|
|||||||
render(id = null) {
|
render(id = null) {
|
||||||
if (this._stopped)
|
if (this._stopped)
|
||||||
return;
|
return;
|
||||||
|
// Invalidate ready event so new handlers wait for this render cycle to complete
|
||||||
|
// This prevents .on('ready') handlers from firing immediately based on previous lifecycle
|
||||||
|
this.invalidate('ready');
|
||||||
// If id provided, delegate to child component
|
// If id provided, delegate to child component
|
||||||
if (id) {
|
if (id) {
|
||||||
const $element = this.$sid(id);
|
const $element = this.$sid(id);
|
||||||
@@ -3233,8 +3236,10 @@ class Jqhtml_Component {
|
|||||||
* @param callback Optional callback to execute when ready
|
* @param callback Optional callback to execute when ready
|
||||||
*/
|
*/
|
||||||
ready(callback) {
|
ready(callback) {
|
||||||
// If already ready, execute callback immediately and resolve
|
// If already ready AND the ready event hasn't been invalidated, resolve immediately
|
||||||
if (this._ready_state >= 4) {
|
// Both conditions must be true: _ready_state >= 4 means we reached ready once,
|
||||||
|
// _lifecycle_states.has('ready') means we haven't started a new reload/render cycle
|
||||||
|
if (this._ready_state >= 4 && this._lifecycle_states.has('ready')) {
|
||||||
if (callback)
|
if (callback)
|
||||||
callback();
|
callback();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -3426,6 +3431,9 @@ class Jqhtml_Component {
|
|||||||
async _reload() {
|
async _reload() {
|
||||||
if (this._stopped)
|
if (this._stopped)
|
||||||
return;
|
return;
|
||||||
|
// Invalidate ready event so new handlers wait for this reload to complete
|
||||||
|
// This prevents .on('ready') handlers from firing immediately based on previous lifecycle
|
||||||
|
this.invalidate('ready');
|
||||||
this._log_lifecycle('reload', 'start');
|
this._log_lifecycle('reload', 'start');
|
||||||
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
|
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
|
||||||
// Just re-render with current data and call on_ready
|
// Just re-render with current data and call on_ready
|
||||||
@@ -3736,6 +3744,25 @@ class Jqhtml_Component {
|
|||||||
const callbacks = this._lifecycle_callbacks.get(event_name);
|
const callbacks = this._lifecycle_callbacks.get(event_name);
|
||||||
return !!(callbacks && callbacks.length > 0);
|
return !!(callbacks && callbacks.length > 0);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Invalidate a lifecycle event - removes the "already occurred" marker
|
||||||
|
*
|
||||||
|
* This is the opposite of trigger(). After invalidate() is called:
|
||||||
|
* - New .on() handlers will NOT fire immediately
|
||||||
|
* - The ready() promise will NOT resolve immediately
|
||||||
|
* - Handlers wait for the next trigger() call
|
||||||
|
*
|
||||||
|
* Existing registered callbacks are NOT removed - they'll fire on next trigger().
|
||||||
|
*
|
||||||
|
* Use case: Call invalidate('ready') at the start of reload() or render()
|
||||||
|
* so that any new .on('ready') handlers wait for the reload/render to complete
|
||||||
|
* rather than firing immediately based on the previous lifecycle's state.
|
||||||
|
*
|
||||||
|
* @param event_name - Name of the event to invalidate
|
||||||
|
*/
|
||||||
|
invalidate(event_name) {
|
||||||
|
this._lifecycle_states.delete(event_name);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Find element by scoped ID
|
* Find element by scoped ID
|
||||||
*
|
*
|
||||||
@@ -4984,7 +5011,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.31';
|
const version = '2.3.32';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/index.cjs.map
generated
vendored
2
node_modules/@jqhtml/core/dist/index.cjs.map
generated
vendored
File diff suppressed because one or more lines are too long
33
node_modules/@jqhtml/core/dist/index.js
generated
vendored
33
node_modules/@jqhtml/core/dist/index.js
generated
vendored
@@ -2680,6 +2680,9 @@ class Jqhtml_Component {
|
|||||||
render(id = null) {
|
render(id = null) {
|
||||||
if (this._stopped)
|
if (this._stopped)
|
||||||
return;
|
return;
|
||||||
|
// Invalidate ready event so new handlers wait for this render cycle to complete
|
||||||
|
// This prevents .on('ready') handlers from firing immediately based on previous lifecycle
|
||||||
|
this.invalidate('ready');
|
||||||
// If id provided, delegate to child component
|
// If id provided, delegate to child component
|
||||||
if (id) {
|
if (id) {
|
||||||
const $element = this.$sid(id);
|
const $element = this.$sid(id);
|
||||||
@@ -3229,8 +3232,10 @@ class Jqhtml_Component {
|
|||||||
* @param callback Optional callback to execute when ready
|
* @param callback Optional callback to execute when ready
|
||||||
*/
|
*/
|
||||||
ready(callback) {
|
ready(callback) {
|
||||||
// If already ready, execute callback immediately and resolve
|
// If already ready AND the ready event hasn't been invalidated, resolve immediately
|
||||||
if (this._ready_state >= 4) {
|
// Both conditions must be true: _ready_state >= 4 means we reached ready once,
|
||||||
|
// _lifecycle_states.has('ready') means we haven't started a new reload/render cycle
|
||||||
|
if (this._ready_state >= 4 && this._lifecycle_states.has('ready')) {
|
||||||
if (callback)
|
if (callback)
|
||||||
callback();
|
callback();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -3422,6 +3427,9 @@ class Jqhtml_Component {
|
|||||||
async _reload() {
|
async _reload() {
|
||||||
if (this._stopped)
|
if (this._stopped)
|
||||||
return;
|
return;
|
||||||
|
// Invalidate ready event so new handlers wait for this reload to complete
|
||||||
|
// This prevents .on('ready') handlers from firing immediately based on previous lifecycle
|
||||||
|
this.invalidate('ready');
|
||||||
this._log_lifecycle('reload', 'start');
|
this._log_lifecycle('reload', 'start');
|
||||||
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
|
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
|
||||||
// Just re-render with current data and call on_ready
|
// Just re-render with current data and call on_ready
|
||||||
@@ -3732,6 +3740,25 @@ class Jqhtml_Component {
|
|||||||
const callbacks = this._lifecycle_callbacks.get(event_name);
|
const callbacks = this._lifecycle_callbacks.get(event_name);
|
||||||
return !!(callbacks && callbacks.length > 0);
|
return !!(callbacks && callbacks.length > 0);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Invalidate a lifecycle event - removes the "already occurred" marker
|
||||||
|
*
|
||||||
|
* This is the opposite of trigger(). After invalidate() is called:
|
||||||
|
* - New .on() handlers will NOT fire immediately
|
||||||
|
* - The ready() promise will NOT resolve immediately
|
||||||
|
* - Handlers wait for the next trigger() call
|
||||||
|
*
|
||||||
|
* Existing registered callbacks are NOT removed - they'll fire on next trigger().
|
||||||
|
*
|
||||||
|
* Use case: Call invalidate('ready') at the start of reload() or render()
|
||||||
|
* so that any new .on('ready') handlers wait for the reload/render to complete
|
||||||
|
* rather than firing immediately based on the previous lifecycle's state.
|
||||||
|
*
|
||||||
|
* @param event_name - Name of the event to invalidate
|
||||||
|
*/
|
||||||
|
invalidate(event_name) {
|
||||||
|
this._lifecycle_states.delete(event_name);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Find element by scoped ID
|
* Find element by scoped ID
|
||||||
*
|
*
|
||||||
@@ -4980,7 +5007,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.31';
|
const version = '2.3.32';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/index.js.map
generated
vendored
2
node_modules/@jqhtml/core/dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
35
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
35
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js
generated
vendored
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* JQHTML Core v2.3.31
|
* JQHTML Core v2.3.32
|
||||||
* (c) 2025 JQHTML Team
|
* (c) 2025 JQHTML Team
|
||||||
* Released under the MIT License
|
* Released under the MIT License
|
||||||
*/
|
*/
|
||||||
@@ -2685,6 +2685,9 @@ class Jqhtml_Component {
|
|||||||
render(id = null) {
|
render(id = null) {
|
||||||
if (this._stopped)
|
if (this._stopped)
|
||||||
return;
|
return;
|
||||||
|
// Invalidate ready event so new handlers wait for this render cycle to complete
|
||||||
|
// This prevents .on('ready') handlers from firing immediately based on previous lifecycle
|
||||||
|
this.invalidate('ready');
|
||||||
// If id provided, delegate to child component
|
// If id provided, delegate to child component
|
||||||
if (id) {
|
if (id) {
|
||||||
const $element = this.$sid(id);
|
const $element = this.$sid(id);
|
||||||
@@ -3234,8 +3237,10 @@ class Jqhtml_Component {
|
|||||||
* @param callback Optional callback to execute when ready
|
* @param callback Optional callback to execute when ready
|
||||||
*/
|
*/
|
||||||
ready(callback) {
|
ready(callback) {
|
||||||
// If already ready, execute callback immediately and resolve
|
// If already ready AND the ready event hasn't been invalidated, resolve immediately
|
||||||
if (this._ready_state >= 4) {
|
// Both conditions must be true: _ready_state >= 4 means we reached ready once,
|
||||||
|
// _lifecycle_states.has('ready') means we haven't started a new reload/render cycle
|
||||||
|
if (this._ready_state >= 4 && this._lifecycle_states.has('ready')) {
|
||||||
if (callback)
|
if (callback)
|
||||||
callback();
|
callback();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -3427,6 +3432,9 @@ class Jqhtml_Component {
|
|||||||
async _reload() {
|
async _reload() {
|
||||||
if (this._stopped)
|
if (this._stopped)
|
||||||
return;
|
return;
|
||||||
|
// Invalidate ready event so new handlers wait for this reload to complete
|
||||||
|
// This prevents .on('ready') handlers from firing immediately based on previous lifecycle
|
||||||
|
this.invalidate('ready');
|
||||||
this._log_lifecycle('reload', 'start');
|
this._log_lifecycle('reload', 'start');
|
||||||
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
|
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
|
||||||
// Just re-render with current data and call on_ready
|
// Just re-render with current data and call on_ready
|
||||||
@@ -3737,6 +3745,25 @@ class Jqhtml_Component {
|
|||||||
const callbacks = this._lifecycle_callbacks.get(event_name);
|
const callbacks = this._lifecycle_callbacks.get(event_name);
|
||||||
return !!(callbacks && callbacks.length > 0);
|
return !!(callbacks && callbacks.length > 0);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Invalidate a lifecycle event - removes the "already occurred" marker
|
||||||
|
*
|
||||||
|
* This is the opposite of trigger(). After invalidate() is called:
|
||||||
|
* - New .on() handlers will NOT fire immediately
|
||||||
|
* - The ready() promise will NOT resolve immediately
|
||||||
|
* - Handlers wait for the next trigger() call
|
||||||
|
*
|
||||||
|
* Existing registered callbacks are NOT removed - they'll fire on next trigger().
|
||||||
|
*
|
||||||
|
* Use case: Call invalidate('ready') at the start of reload() or render()
|
||||||
|
* so that any new .on('ready') handlers wait for the reload/render to complete
|
||||||
|
* rather than firing immediately based on the previous lifecycle's state.
|
||||||
|
*
|
||||||
|
* @param event_name - Name of the event to invalidate
|
||||||
|
*/
|
||||||
|
invalidate(event_name) {
|
||||||
|
this._lifecycle_states.delete(event_name);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Find element by scoped ID
|
* Find element by scoped ID
|
||||||
*
|
*
|
||||||
@@ -4985,7 +5012,7 @@ function init(jQuery) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Version - will be replaced during build with actual version from package.json
|
// Version - will be replaced during build with actual version from package.json
|
||||||
const version = '2.3.31';
|
const version = '2.3.32';
|
||||||
// Default export with all functionality
|
// Default export with all functionality
|
||||||
const jqhtml = {
|
const jqhtml = {
|
||||||
// Core
|
// Core
|
||||||
|
|||||||
2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map
generated
vendored
2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
node_modules/@jqhtml/core/package.json
generated
vendored
2
node_modules/@jqhtml/core/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/core",
|
"name": "@jqhtml/core",
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"description": "Core runtime library for JQHTML",
|
"description": "Core runtime library for JQHTML",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
2
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
@@ -1377,7 +1377,7 @@ export class CodeGenerator {
|
|||||||
for (const [name, component] of this.components) {
|
for (const [name, component] of this.components) {
|
||||||
code += `// Component: ${name}\n`;
|
code += `// Component: ${name}\n`;
|
||||||
code += `jqhtml_components.set('${name}', {\n`;
|
code += `jqhtml_components.set('${name}', {\n`;
|
||||||
code += ` _jqhtml_version: '2.3.31',\n`; // Version will be replaced during build
|
code += ` _jqhtml_version: '2.3.32',\n`; // Version will be replaced during build
|
||||||
code += ` name: '${name}',\n`;
|
code += ` name: '${name}',\n`;
|
||||||
code += ` tag: '${component.tagName}',\n`;
|
code += ` tag: '${component.tagName}',\n`;
|
||||||
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
||||||
|
|||||||
2
node_modules/@jqhtml/parser/package.json
generated
vendored
2
node_modules/@jqhtml/parser/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/parser",
|
"name": "@jqhtml/parser",
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"description": "JQHTML template parser - converts templates to JavaScript",
|
"description": "JQHTML template parser - converts templates to JavaScript",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
2
node_modules/@jqhtml/ssr/package.json
generated
vendored
2
node_modules/@jqhtml/ssr/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@jqhtml/ssr",
|
"name": "@jqhtml/ssr",
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"description": "Server-Side Rendering for JQHTML components - renders components to HTML for SEO",
|
"description": "Server-Side Rendering for JQHTML components - renders components to HTML for SEO",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
2
node_modules/@jqhtml/vscode-extension/.version
generated
vendored
@@ -1 +1 @@
|
|||||||
2.3.31
|
2.3.32
|
||||||
|
|||||||
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.31.vsix → node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.32.vsix
generated
vendored
Executable file → Normal file
BIN
node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.31.vsix → node_modules/@jqhtml/vscode-extension/jqhtml-vscode-extension-2.3.32.vsix
generated
vendored
Executable file → Normal file
Binary file not shown.
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
2
node_modules/@jqhtml/vscode-extension/package.json
generated
vendored
@@ -2,7 +2,7 @@
|
|||||||
"name": "@jqhtml/vscode-extension",
|
"name": "@jqhtml/vscode-extension",
|
||||||
"displayName": "JQHTML",
|
"displayName": "JQHTML",
|
||||||
"description": "Syntax highlighting and language support for JQHTML template files",
|
"description": "Syntax highlighting and language support for JQHTML template files",
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"publisher": "jqhtml",
|
"publisher": "jqhtml",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
24
package-lock.json
generated
24
package-lock.json
generated
@@ -2660,9 +2660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/core": {
|
"node_modules/@jqhtml/core": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.32.tgz",
|
||||||
"integrity": "sha512-VbTAbFF8QVOljNjf+1OZ4cFTE3NaeNzyMidqrooYSsHZvb6Ja5NIMDft+M4FxeidrMoRIwa7QN09XgiJWBVNRg==",
|
"integrity": "sha512-g2BZLfNI4vGS35eR+qVP3WJHMcvYsun8ZFLMlVOB58CnJfdCMeXLhiXZrGr3OFFoxAPaitOG2rmzwqMXKfCvOQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
@@ -2686,9 +2686,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/parser": {
|
"node_modules/@jqhtml/parser": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.32.tgz",
|
||||||
"integrity": "sha512-ILV1onWn+rMdwaPd6DYIPM0dj2aUExTJ4ww4c0/+h3Zk50gnxMJQc6fOirDrTB1nWwKtY19yFDMPFYnurOJ2wA==",
|
"integrity": "sha512-YCycfJkxFyr8PsWH8o0wJBImcCi6Eq4m1XFlMDXwAYlBre3H/GkeYpe3JN7xdYP5f/T4Q8RolBW2Y0vzywl27g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.11",
|
||||||
@@ -2726,9 +2726,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/ssr": {
|
"node_modules/@jqhtml/ssr": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/ssr/-/ssr-2.3.32.tgz",
|
||||||
"integrity": "sha512-EpZ597l/3MEgpvAJTqcZ81cVTJwYxJzs8BLXAdbnQY+ySeOYQL8ot31IV3hdS8b6FnYezzaHN+jSBtqZZsAYnQ==",
|
"integrity": "sha512-VFz3rPSdPn8ZZg36Cb8XujALNXXIyTxMdj8kNhNNhcqBWvKn8XxygyZrGCPCpO9cyRJKlhzDpEX+tW4qMqvPng==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
@@ -2822,9 +2822,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jqhtml/vscode-extension": {
|
"node_modules/@jqhtml/vscode-extension": {
|
||||||
"version": "2.3.31",
|
"version": "2.3.32",
|
||||||
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.31.tgz",
|
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.32.tgz",
|
||||||
"integrity": "sha512-aQoKAxLz1ziuJ6I2EfFXxUBvPEsDxirR2q/6VwEzYqfVTHdKiw6M2Sk25Nhm/lrg5dNLuiKa+snRodV8yEOWpQ==",
|
"integrity": "sha512-UL/7JpIi2BS1QLzj60kYg/Womf7O+UHjK0c2/JnSowl813FUmDFgA4DWog1DlUllbUnf4cRvY5MX302FibqGHw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.74.0"
|
"vscode": "^1.74.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user