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:
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user