Add <%br= %> jqhtml syntax docs, class override detection, npm update

Document event handler placement and model fetch clarification

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-15 10:16:06 +00:00
parent 61f8f058f2
commit 1594502cb2
791 changed files with 7044 additions and 6089 deletions

View File

@@ -242,9 +242,10 @@ export class Parser {
const token = this.previous();
return createNode(NodeType.TEXT, { content: token.value }, token.start, token.end, token.line, token.column, this.create_location(token, token));
}
// Expression <%= ... %> or <%!= ... %>
// Expression <%= ... %> or <%!= ... %> or <%br= ... %>
if (this.match(TokenType.EXPRESSION_START) ||
this.match(TokenType.EXPRESSION_UNESCAPED)) {
this.match(TokenType.EXPRESSION_UNESCAPED) ||
this.match(TokenType.EXPRESSION_BR)) {
return this.parse_expression();
}
// Code block <% ... %>
@@ -270,16 +271,22 @@ export class Parser {
}
return null;
}
// Parse <%= expression %> or <%!= expression %>
// Parse <%= expression %> or <%!= expression %> or <%br= expression %>
parse_expression() {
const start_token = this.previous(); // EXPRESSION_START or EXPRESSION_UNESCAPED
const start_token = this.previous(); // EXPRESSION_START, EXPRESSION_UNESCAPED, or EXPRESSION_BR
const code_token = this.consume(TokenType.JAVASCRIPT, 'Expected JavaScript code');
// Validate JavaScript code for common mistakes
this.validate_javascript_code(code_token.value, code_token);
const end_token = this.consume(TokenType.TAG_END, 'Expected %>');
// EXPRESSION_START and EXPRESSION_BR are escaped, EXPRESSION_UNESCAPED is not
const is_escaped = start_token.type === TokenType.EXPRESSION_START ||
start_token.type === TokenType.EXPRESSION_BR;
// Only EXPRESSION_BR converts newlines to <br />
const is_nl2br = start_token.type === TokenType.EXPRESSION_BR;
return createNode(NodeType.EXPRESSION, {
code: code_token.value,
escaped: start_token.type === TokenType.EXPRESSION_START
escaped: is_escaped,
nl2br: is_nl2br
}, start_token.start, end_token.end, start_token.line, start_token.column, this.create_location(start_token, end_token));
}
// Parse <% code %> - collect tokens with their types for proper transformation
@@ -529,7 +536,8 @@ export class Parser {
// Check if this is a compound value with interpolation
if (this.check(TokenType.ATTR_VALUE) ||
this.check(TokenType.EXPRESSION_START) ||
this.check(TokenType.EXPRESSION_UNESCAPED)) {
this.check(TokenType.EXPRESSION_UNESCAPED) ||
this.check(TokenType.EXPRESSION_BR)) {
value = this.parse_attribute_value();
}
}
@@ -618,11 +626,13 @@ export class Parser {
const firstToken = this.peek();
const isSimpleValue = this.check(TokenType.ATTR_VALUE) &&
!this.check_ahead(1, TokenType.EXPRESSION_START) &&
!this.check_ahead(1, TokenType.EXPRESSION_UNESCAPED);
!this.check_ahead(1, TokenType.EXPRESSION_UNESCAPED) &&
!this.check_ahead(1, TokenType.EXPRESSION_BR);
// Collect all parts of the attribute value
while (this.check(TokenType.ATTR_VALUE) ||
this.check(TokenType.EXPRESSION_START) ||
this.check(TokenType.EXPRESSION_UNESCAPED)) {
this.check(TokenType.EXPRESSION_UNESCAPED) ||
this.check(TokenType.EXPRESSION_BR)) {
if (this.check(TokenType.ATTR_VALUE)) {
const token = this.advance();
// Preserve whitespace in interpolated attribute values - spaces between
@@ -633,12 +643,15 @@ export class Parser {
}
}
else if (this.check(TokenType.EXPRESSION_START) ||
this.check(TokenType.EXPRESSION_UNESCAPED)) {
const is_escaped = this.peek().type === TokenType.EXPRESSION_START;
this.advance(); // consume <%= or <%!=
this.check(TokenType.EXPRESSION_UNESCAPED) ||
this.check(TokenType.EXPRESSION_BR)) {
const token_type = this.peek().type;
const is_escaped = token_type === TokenType.EXPRESSION_START || token_type === TokenType.EXPRESSION_BR;
const is_nl2br = token_type === TokenType.EXPRESSION_BR;
this.advance(); // consume <%=, <%!=, or <%br=
const expr_token = this.consume(TokenType.JAVASCRIPT, 'Expected expression');
this.consume(TokenType.TAG_END, 'Expected %>');
parts.push({ type: 'expression', value: expr_token.value, escaped: is_escaped });
parts.push({ type: 'expression', value: expr_token.value, escaped: is_escaped, nl2br: is_nl2br });
}
}
// If it's a single text part, check if it's quoted
@@ -769,7 +782,7 @@ export class Parser {
const token = this.peek();
// Special case: Detecting template expressions inside HTML tag attributes
if (type === TokenType.GT &&
(token.type === TokenType.EXPRESSION_START || token.type === TokenType.EXPRESSION_UNESCAPED)) {
(token.type === TokenType.EXPRESSION_START || token.type === TokenType.EXPRESSION_UNESCAPED || token.type === TokenType.EXPRESSION_BR)) {
const error = syntaxError('Template expressions (<% %>) cannot be used as attribute values inside HTML tags', token.line, token.column, this.source, this.filename);
// Add helpful remediation examples
error.message += '\n\n' +