Fix code quality violations and add VS Code extension features

Fix VS Code extension storage paths for new directory structure
Fix jqhtml compiled files missing from bundle
Fix bundle babel transformation and add rsxrealpath() function

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-22 00:43:05 +00:00
parent 53d359bc91
commit 37a6183eb4
80 changed files with 1066 additions and 255 deletions

View File

@@ -98,8 +98,14 @@ class JQueryLengthCheck_CodeQualityRule extends CodeQualityRule_Abstract
}
if ($found) {
// Check if .length is followed by comparison or assignment operators
// These are valid uses: .length > 1, .length = x, etc.
if (preg_match('/\.length\s*([><=!]+|[+\-*\/]=)/', $sanitized_line)) {
continue; // Skip - this is a numeric comparison or assignment
}
$original_line = $original_lines[$line_num] ?? $sanitized_line;
$this->add_violation(
$file_path,
$line_number,

View File

@@ -51,13 +51,20 @@ class JQueryVariableNaming_CodeQualityRule extends CodeQualityRule_Abstract
];
/**
* jQuery methods that return scalar values (not jQuery objects)
* jQuery methods that return scalar values ONLY when called as getters (no arguments)
* When called with arguments, these return jQuery object for chaining
*/
private const SCALAR_METHODS = [
private const GETTER_METHODS = [
'data', 'attr', 'val', 'text', 'html', 'prop', 'css',
'offset', 'position', 'scrollTop', 'scrollLeft',
'width', 'height', 'innerWidth', 'innerHeight',
'outerWidth', 'outerHeight',
];
/**
* jQuery methods that ALWAYS return scalar values
*/
private const SCALAR_METHODS = [
'index', 'size', 'length', 'get', 'toArray',
'serialize', 'serializeArray',
'is', 'hasClass', 'is_visible' // Custom RSpade methods
@@ -155,7 +162,23 @@ class JQueryVariableNaming_CodeQualityRule extends CodeQualityRule_Abstract
// Direct jQuery selector: $(...)
if (preg_match('/^\$\s*\(/', $expr)) {
// Check if followed by method chain
// Check if it's creating an element: $('<element>')
if (preg_match('/^\$\s*\(\s*[\'"]</', $expr)) {
// Creating jQuery element - always returns jQuery object
// Even with method chains like .text() or .attr(), the chaining continues
// We only care about method chains that END with scalar methods
if (preg_match('/^\$\s*\([^)]*\)(.*)/', $expr, $matches)) {
$chain = trim($matches[1]);
if ($chain === '') {
return 'jquery'; // Just $('<element>') with no methods
}
// Only check if chain ENDS with a scalar method
return $this->analyze_method_chain($chain);
}
return 'jquery';
}
// Regular selector or other jQuery call
if (preg_match('/^\$\s*\([^)]*\)(.*)/', $expr, $matches)) {
$chain = trim($matches[1]);
if ($chain === '') {
@@ -201,25 +224,57 @@ class JQueryVariableNaming_CodeQualityRule extends CodeQualityRule_Abstract
// Find the last method call in the chain
// Match patterns like .method() or .method(args)
// Also capture what's inside the parentheses
$methods = [];
preg_match_all('/\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)/', $chain, $methods);
if (empty($methods[1])) {
preg_match_all('/\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\)/', $chain, $methods, PREG_SET_ORDER);
if (empty($methods)) {
// No method calls found
return 'unknown';
}
// Check the last method to determine return type
$last_method = end($methods[1]);
$last_method_data = end($methods);
$last_method = $last_method_data[1];
$last_args = trim($last_method_data[2] ?? '');
if (in_array($last_method, self::JQUERY_OBJECT_METHODS, true)) {
return 'jquery';
}
if (in_array($last_method, self::SCALAR_METHODS, true)) {
return 'scalar';
}
// Check getter methods - return scalar for getters, jQuery for setters
if (in_array($last_method, self::GETTER_METHODS, true)) {
// Count arguments by splitting on commas (simple heuristic)
// Note: This won't handle nested function calls perfectly, but works for common cases
$arg_count = $last_args === '' ? 0 : (substr_count($last_args, ',') + 1);
// Special handling for methods that take a key parameter
// .data('key') - 1 arg = getter (returns value)
// .data('key', value) - 2 args = setter (returns jQuery)
// .attr('name') - 1 arg = getter (returns attribute value)
// .attr('name', value) - 2 args = setter (returns jQuery)
if (in_array($last_method, ['data', 'attr', 'prop', 'css'], true)) {
if ($arg_count <= 1) {
return 'scalar'; // Getter with key - returns scalar value
} else {
return 'jquery'; // Setter with key and value - returns jQuery for chaining
}
}
// For other getter methods (text, html, val, etc.)
// .text() - no args = getter (returns text)
// .text('value') - 1 arg = setter (returns jQuery)
if ($last_args === '') {
return 'scalar'; // Getter mode - returns scalar
} else {
return 'jquery'; // Setter mode - returns jQuery object for chaining
}
}
// Unknown method - could be custom plugin
return 'unknown';
}

View File

@@ -7,23 +7,24 @@ use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
/**
* JavaScript 'this' Usage Rule
*
* PHILOSOPHY: Remove ambiguity about what 'this' refers to in all contexts.
* PHILOSOPHY: Enforce clear 'this' patterns in anonymous functions and static methods.
*
* RULES:
* 1. Anonymous functions: Can use 'const $var = $(this)' as first line (jQuery pattern)
* 2. Instance methods: Must use 'const that = this' as first line (constructors exempt)
* 3. Static methods: Use Class_Name OR 'const CurrentClass = this' for polymorphism
* 4. Arrow functions: Ignored (they inherit 'this' context)
* 5. Constructors: Direct 'this' usage allowed for property assignment
* 1. Anonymous functions: MUST use 'const $element = $(this)' or 'const that = this' as first line
* 2. Static methods: MUST NOT use naked 'this' - use Class_Name or 'const CurrentClass = this'
* 3. Instance methods: EXEMPT - can use 'this' directly (no aliasing required)
* 4. Arrow functions: EXEMPT - they inherit 'this' context
* 5. Constructors: EXEMPT - 'this' allowed directly for property assignment
*
* PATTERNS:
* - jQuery: const $element = $(this) // Variable must start with $
* - Instance: const that = this // Standard instance aliasing
* - Static (exact): Use Class_Name // When you need exact class
* - jQuery callback: const $element = $(this) // Variable must start with $
* - Anonymous function: const that = this // Instance context aliasing
* - Static (exact): Use Class_Name // When you need exact class
* - Static (polymorphic): const CurrentClass = this // When inherited classes need different behavior
*
* This rule does NOT try to detect all jQuery callbacks - it offers the jQuery
* pattern as an option when 'this' violations are found in anonymous functions.
* INSTANCE METHODS POLICY:
* Instance methods (on_ready, on_load, etc.) can use 'this' directly.
* This rule only enforces aliasing for anonymous functions and prohibits naked 'this' in static methods.
*/
class ThisUsage_CodeQualityRule extends CodeQualityRule_Abstract
{

View File

@@ -159,7 +159,22 @@ function analyzeFile(filePath) {
let isAnonymousFunc = false;
let isStaticMethod = false;
let isConstructor = false;
let isInstanceMethod = false;
let hasMethodDefinition = false;
// First pass: check if we're in a MethodDefinition
for (let i = ancestors.length - 1; i >= 0; i--) {
const ancestor = ancestors[i];
if (ancestor.type === 'MethodDefinition') {
hasMethodDefinition = true;
isStaticMethod = ancestor.static;
isConstructor = ancestor.kind === 'constructor';
isInstanceMethod = !ancestor.static && ancestor.kind !== 'constructor';
break;
}
}
// Second pass: find function and class
for (let i = ancestors.length - 1; i >= 0; i--) {
const ancestor = ancestors[i];
@@ -168,7 +183,8 @@ function analyzeFile(filePath) {
ancestor.type === 'FunctionDeclaration'
)) {
containingFunc = ancestor;
isAnonymousFunc = ancestor.type === 'FunctionExpression';
// Only mark as anonymous if NOT inside a MethodDefinition
isAnonymousFunc = ancestor.type === 'FunctionExpression' && !hasMethodDefinition;
}
if (!containingClass && (
@@ -177,11 +193,6 @@ function analyzeFile(filePath) {
)) {
containingClass = ancestor;
}
if (ancestor.type === 'MethodDefinition') {
isStaticMethod = ancestor.static;
isConstructor = ancestor.kind === 'constructor';
}
}
if (!containingFunc) {
@@ -193,6 +204,12 @@ function analyzeFile(filePath) {
return;
}
// Skip instance methods - 'this' is allowed directly in instance methods
// Only enforce aliasing for anonymous functions and static methods
if (isInstanceMethod) {
return;
}
// Check if this is part of the allowed first-line pattern with const
const parent = ancestors[ancestors.length - 2];
const firstStmt = containingFunc.body?.body?.[0];
@@ -323,23 +340,9 @@ function analyzeFile(filePath) {
if (isAnonymousFunc && !pattern) {
remediation += `\nException: If this is a jQuery callback, add 'const $element = $(this);' as the first line.`;
}
} else {
// Instance method
if (!pattern) {
message = `Instance method in '${className}' must alias 'this' for clarity.`;
remediation = `Add 'const that = this;' as the first line of this method, then use 'that' instead of 'this'.\n` +
`This applies even to ORM models and similar classes where direct property access is common.\n` +
`Note: Constructors are exempt - 'this' is allowed directly in constructors for property assignment.\n` +
`Example: Instead of 'return this.name;' use 'const that = this; return that.name;'`;
} else if (pattern === 'that-pattern') {
message = `'this' used after aliasing to 'that'. Use 'that' instead.`;
remediation = `You already have 'const that = this'. Use 'that' consistently throughout the method.\n` +
`All property access should use 'that.property' not 'this.property'.`;
} else if (pattern === 'that-pattern-wrong-kind') {
message = `Instance alias must use 'const', not 'let' or 'var'.`;
remediation = `Change to 'const that = this;' - the instance reference should never be reassigned.`;
}
}
// NOTE: Instance methods are exempt from this rule - they can use 'this' directly
// The check returns early for instance methods, so this else block is unreachable for them
if (message) {
violations.push({