Fix code quality violations and enhance ROUTE-EXISTS-01 rule

Implement JQHTML function cache ID system and fix bundle compilation
Implement underscore prefix for system tables
Fix JS syntax linter to support decorators and grant exception to Task system
SPA: Update planning docs and wishlists with remaining features
SPA: Document Navigation API abandonment and future enhancements
Implement SPA browser integration with History API (Phase 1)
Convert contacts view page to SPA action
Convert clients pages to SPA actions and document conversion procedure
SPA: Merge GET parameters and update documentation
Implement SPA route URL generation in JavaScript and PHP
Implement SPA bootstrap controller architecture
Add SPA routing manual page (rsx:man spa)
Add SPA routing documentation to CLAUDE.md
Phase 4 Complete: Client-side SPA routing implementation
Update get_routes() consumers for unified route structure
Complete SPA Phase 3: PHP-side route type detection and is_spa flag
Restore unified routes structure and Manifest_Query class
Refactor route indexing and add SPA infrastructure
Phase 3 Complete: SPA route registration in manifest
Implement SPA Phase 2: Extract router code and test decorators
Rename Jqhtml_Component to Component and complete SPA foundation setup

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-19 17:48:15 +00:00
parent 77b4d10af8
commit 9ebcc359ae
4360 changed files with 37751 additions and 18578 deletions

View File

@@ -302,113 +302,6 @@ export class Parser {
return createNode(NodeType.CODE_BLOCK, { tokens }, // Pass tokens array instead of concatenated code
start_token.start, end_token.end, start_token.line, start_token.column, this.create_location(start_token, end_token));
}
// Parse if statement with colon style only
parse_if_statement(start_token) {
console.log('[Parser] Parsing if statement at line', start_token.line);
this.consume(TokenType.IF, 'Expected if');
const condition_token = this.consume(TokenType.JAVASCRIPT, 'Expected condition');
console.log('[Parser] If condition:', condition_token.value);
this.consume(TokenType.TAG_END, 'Expected %>');
const consequent = [];
let alternate = null;
// Parse consequent branch
while (!this.check_sequence(TokenType.CODE_START, TokenType.ELSE) &&
!this.check_sequence(TokenType.CODE_START, TokenType.ENDIF)) {
if (this.is_at_end()) {
const error = unclosedError('if statement', `if (${condition_token.value})`, start_token.line, start_token.column, this.source, this.filename);
error.message += getSuggestion(error.message);
throw error;
}
const node = this.parse_content();
if (node) {
consequent.push(node);
}
}
// Check for else branch
if (this.check_sequence(TokenType.CODE_START, TokenType.ELSE)) {
console.log('[Parser] Found else branch');
this.advance(); // CODE_START
this.advance(); // ELSE
// Check if this is an "else if"
if (this.check(TokenType.IF)) {
console.log('[Parser] This is an else if statement');
// This is an else if - put back the ELSE token and parse as new if statement
this.current--; // Put back ELSE
this.current--; // Put back CODE_START
// Parse the else if as a new if statement
alternate = [];
const elseIfNode = this.parse_content();
if (elseIfNode) {
alternate.push(elseIfNode);
}
}
else {
// Regular else branch
// Skip optional trailing code
if (this.check(TokenType.JAVASCRIPT)) {
this.advance();
}
this.consume(TokenType.TAG_END, 'Expected %>');
alternate = [];
// Parse else branch
while (!this.check_sequence(TokenType.CODE_START, TokenType.ENDIF)) {
if (this.is_at_end()) {
const error = unclosedError('if statement (in else branch)', `if (${condition_token.value})`, start_token.line, start_token.column, this.source, this.filename);
error.message += getSuggestion(error.message);
throw error;
}
const node = this.parse_content();
if (node) {
alternate.push(node);
}
}
}
}
// Consume endif
this.consume(TokenType.CODE_START, 'Expected <%');
this.consume(TokenType.ENDIF, 'Expected endif');
// Skip optional semicolon
if (this.check(TokenType.JAVASCRIPT)) {
this.advance();
}
const end_token = this.consume(TokenType.TAG_END, 'Expected %>');
return createNode(NodeType.IF_STATEMENT, {
condition: condition_token.value,
consequent,
alternate
}, start_token.start, end_token.end, start_token.line, start_token.column, this.create_location(start_token, end_token));
}
// Parse for loop
parse_for_statement(start_token) {
this.consume(TokenType.FOR, 'Expected for');
const iterator_token = this.consume(TokenType.JAVASCRIPT, 'Expected iterator expression');
this.consume(TokenType.TAG_END, 'Expected %>');
const body = [];
// Parse loop body
while (!this.check_sequence(TokenType.CODE_START, TokenType.ENDFOR)) {
if (this.is_at_end()) {
const error = unclosedError('for statement', `for ${iterator_token.value}`, start_token.line, start_token.column, this.source, this.filename);
error.message += getSuggestion(error.message);
throw error;
}
const node = this.parse_content();
if (node) {
body.push(node);
}
}
// Consume endfor
this.consume(TokenType.CODE_START, 'Expected <%');
this.consume(TokenType.ENDFOR, 'Expected endfor');
// Skip optional semicolon
if (this.check(TokenType.JAVASCRIPT)) {
this.advance();
}
const end_token = this.consume(TokenType.TAG_END, 'Expected %>');
return createNode(NodeType.FOR_STATEMENT, {
iterator: iterator_token.value,
body
}, start_token.start, end_token.end, start_token.line, start_token.column, this.create_location(start_token, end_token));
}
// JavaScript reserved words that cannot be used as slot names
static JAVASCRIPT_RESERVED_WORDS = new Set([
// Keywords
@@ -642,10 +535,12 @@ export class Parser {
}
// Handle special attribute prefixes
if (name.startsWith('$')) {
// General case: $property becomes data-property
// This includes $id → data-id (for scoped IDs)
// The distinction between data-id (scoped) and id (pass-through) is preserved
name = 'data-' + name.substring(1);
// Special case: $id becomes data-id (needed for scoped ID system)
// All other $ attributes stay as-is (handled by instruction-processor.ts)
if (name === '$id') {
name = 'data-id';
}
// Keep $ prefix for other attributes - they get stored via .data() at runtime
// Keep the value object intact to preserve quoted/unquoted distinction
}
else if (name.startsWith(':')) {
@@ -655,9 +550,9 @@ export class Parser {
// Keep the value object intact to preserve quoted/unquoted distinction
}
else if (name.startsWith('@')) {
// Event binding: @click="handler" becomes data-on-click
// Event binding: @click="handler" becomes data-__-on-click
// Preserve whether value was quoted or not for proper code generation
name = 'data-on-' + name.substring(1);
name = 'data-__-on-' + name.substring(1);
// Keep the value object intact to preserve quoted/unquoted distinction
}
attributes[name] = value;
@@ -674,11 +569,8 @@ export class Parser {
// Consume <%
this.consume(TokenType.CODE_START, 'Expected <%');
let condition;
// Two possibilities:
// 1. Brace style: CODE_START → JAVASCRIPT "if (condition) {" → TAG_END
// 2. Colon style: CODE_START → IF → JAVASCRIPT "(condition)" → TAG_END
// Only brace style supported: CODE_START → JAVASCRIPT "if (condition) {" → TAG_END
if (this.check(TokenType.JAVASCRIPT)) {
// Brace style
const jsToken = this.consume(TokenType.JAVASCRIPT, 'Expected if statement');
const jsCode = jsToken.value.trim();
// Verify it starts with 'if' and contains both ( and {
@@ -694,12 +586,6 @@ export class Parser {
// Extract just the condition part (between parens, including parens)
condition = jsCode.substring(openParen, closeBrace).trim();
}
else if (this.check(TokenType.IF)) {
// Colon style
this.advance(); // consume 'if'
const jsToken = this.consume(TokenType.JAVASCRIPT, 'Expected condition after if');
condition = jsToken.value.trim();
}
else {
// Not an if statement
throw syntaxError('Only if statements are allowed in attribute context. Use <% if (condition) { %>attr="value"<% } %>', this.peek().line, this.peek().column, this.source);
@@ -783,8 +669,8 @@ export class Parser {
// Return as an identifier expression (function call chain)
return { identifier: true, value: value };
}
// Otherwise, treat as a literal string value
return value;
// Otherwise, treat as a JavaScript expression (includes numeric literals like 42, 3.14, etc.)
return { expression: true, value: value };
}
// Any expression or multiple parts needs interpolation handling
return { interpolated: true, parts };