Fix for loop parsing and add @if/@for in attribute positions

This commit is contained in:
Michael Netshipise 2026-01-19 08:13:00 +02:00
parent 6d858472b2
commit b6a168a2b6
4 changed files with 17798 additions and 16599 deletions

View File

@ -9,7 +9,6 @@ module.exports = grammar({
conflicts: ($) => [
[$.expression, $.pattern],
[$.rust_path, $.expression],
[$.struct_pattern, $.expression],
[$.html_element],
[$.self_closing_function_tag, $.container_function_tag],
],
@ -120,12 +119,14 @@ module.exports = grammar({
content_block: ($) => seq("{", repeat($.template_node), "}"),
// Template nodes
// Note: template_control_flow must come before template_expression
// to avoid @for/@if/@let being parsed as variable expressions
template_node: ($) =>
choice(
$.html_element,
$.function_tag,
$.template_expression,
$.template_control_flow,
$.template_expression,
$.comment,
$.embedded_language,
$.escape_at,
@ -133,17 +134,18 @@ module.exports = grammar({
),
// HTML elements
// Note: attribute_or_control allows @if/@for in attribute position
html_element: ($) =>
choice(
// Self-closing tag
seq("<", $.tag_name, repeat($.html_attribute), "/", ">"),
seq("<", $.tag_name, repeat($.attribute_or_control), "/", ">"),
// Void elements (no closing tag needed)
seq("<", $.tag_name, repeat($.html_attribute), ">"),
seq("<", $.tag_name, repeat($.attribute_or_control), ">"),
// Full element with content and closing tag
seq(
"<",
$.tag_name,
repeat($.html_attribute),
repeat($.attribute_or_control),
">",
repeat($.template_node),
"</",
@ -152,11 +154,47 @@ module.exports = grammar({
),
),
// Allow either HTML attributes or control flow (@if/@for) in attribute position
attribute_or_control: ($) =>
choice(
$.html_attribute,
$.attribute_control_flow,
),
// Control flow in attribute context - produces attributes conditionally
attribute_control_flow: ($) =>
choice(
$.attribute_if_statement,
$.attribute_for_loop,
),
attribute_if_statement: ($) =>
seq(
"@if",
$.expression,
"{",
repeat($.attribute_or_control),
"}",
optional(seq("else", "{", repeat($.attribute_or_control), "}")),
),
attribute_for_loop: ($) =>
seq(
"@for",
$.simple_pattern,
"in",
$.expression,
"{",
repeat($.attribute_or_control),
"}",
),
tag_name: ($) => /[a-zA-Z][a-zA-Z0-9-]*/,
html_attribute: ($) =>
seq($.attribute_name, optional(seq("=", $.attribute_value))),
// Attribute names: allow @ for directives like @click, but not @if/@for/@let
attribute_name: ($) => /[a-zA-Z_:@][a-zA-Z0-9_:.-]*/,
attribute_value: ($) =>
@ -236,7 +274,7 @@ module.exports = grammar({
seq(
"@for",
optional(seq($.identifier, ":")),
$.pattern,
$.simple_pattern,
"in",
$.expression,
$.content_block,
@ -312,14 +350,23 @@ module.exports = grammar({
unquoted_value: ($) => /[^\s>=\/]+/,
// Patterns
// Patterns - full patterns used in match arms
pattern: ($) =>
choice(
$.wildcard_pattern,
$.tuple_pattern,
$.struct_pattern,
$.identifier_pattern,
$.literal,
$.identifier_pattern,
),
// Simple pattern for for loops - no struct patterns to avoid ambiguity with content_block
simple_pattern: ($) =>
choice(
$.wildcard_pattern,
$.tuple_pattern,
$.literal,
$.identifier_pattern,
),
wildcard_pattern: ($) => "_",

View File

@ -607,11 +607,11 @@
},
{
"type": "SYMBOL",
"name": "template_expression"
"name": "template_control_flow"
},
{
"type": "SYMBOL",
"name": "template_control_flow"
"name": "template_expression"
},
{
"type": "SYMBOL",
@ -649,7 +649,7 @@
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "html_attribute"
"name": "attribute_or_control"
}
},
{
@ -677,7 +677,7 @@
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "html_attribute"
"name": "attribute_or_control"
}
},
{
@ -701,7 +701,7 @@
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "html_attribute"
"name": "attribute_or_control"
}
},
{
@ -731,6 +731,128 @@
}
]
},
"attribute_or_control": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "html_attribute"
},
{
"type": "SYMBOL",
"name": "attribute_control_flow"
}
]
},
"attribute_control_flow": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "attribute_if_statement"
},
{
"type": "SYMBOL",
"name": "attribute_for_loop"
}
]
},
"attribute_if_statement": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "@if"
},
{
"type": "SYMBOL",
"name": "expression"
},
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "attribute_or_control"
}
},
{
"type": "STRING",
"value": "}"
},
{
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "else"
},
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "attribute_or_control"
}
},
{
"type": "STRING",
"value": "}"
}
]
},
{
"type": "BLANK"
}
]
}
]
},
"attribute_for_loop": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "@for"
},
{
"type": "SYMBOL",
"name": "simple_pattern"
},
{
"type": "STRING",
"value": "in"
},
{
"type": "SYMBOL",
"name": "expression"
},
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "attribute_or_control"
}
},
{
"type": "STRING",
"value": "}"
}
]
},
"tag_name": {
"type": "PATTERN",
"value": "[a-zA-Z][a-zA-Z0-9-]*"
@ -1172,7 +1294,7 @@
},
{
"type": "SYMBOL",
"name": "pattern"
"name": "simple_pattern"
},
{
"type": "STRING",
@ -1578,13 +1700,34 @@
"type": "SYMBOL",
"name": "struct_pattern"
},
{
"type": "SYMBOL",
"name": "literal"
},
{
"type": "SYMBOL",
"name": "identifier_pattern"
}
]
},
"simple_pattern": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "wildcard_pattern"
},
{
"type": "SYMBOL",
"name": "tuple_pattern"
},
{
"type": "SYMBOL",
"name": "literal"
},
{
"type": "SYMBOL",
"name": "identifier_pattern"
}
]
},
@ -2830,10 +2973,6 @@
"rust_path",
"expression"
],
[
"struct_pattern",
"expression"
],
[
"html_element"
],

View File

@ -67,6 +67,67 @@
]
}
},
{
"type": "attribute_control_flow",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "attribute_for_loop",
"named": true
},
{
"type": "attribute_if_statement",
"named": true
}
]
}
},
{
"type": "attribute_for_loop",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "attribute_or_control",
"named": true
},
{
"type": "expression",
"named": true
},
{
"type": "simple_pattern",
"named": true
}
]
}
},
{
"type": "attribute_if_statement",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "attribute_or_control",
"named": true
},
{
"type": "expression",
"named": true
}
]
}
},
{
"type": "attribute_list",
"named": true,
@ -82,6 +143,25 @@
]
}
},
{
"type": "attribute_or_control",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "attribute_control_flow",
"named": true
},
{
"type": "html_attribute",
"named": true
}
]
}
},
{
"type": "attribute_reference",
"named": true,
@ -542,7 +622,7 @@
"named": true
},
{
"type": "pattern",
"type": "simple_pattern",
"named": true
}
]
@ -718,7 +798,7 @@
"required": true,
"types": [
{
"type": "html_attribute",
"type": "attribute_or_control",
"named": true
},
{
@ -1230,6 +1310,33 @@
]
}
},
{
"type": "simple_pattern",
"named": true,
"fields": {},
"children": {
"multiple": false,
"required": true,
"types": [
{
"type": "identifier_pattern",
"named": true
},
{
"type": "literal",
"named": true
},
{
"type": "tuple_pattern",
"named": true
},
{
"type": "wildcard_pattern",
"named": true
}
]
}
},
{
"type": "slice_type",
"named": true,

34064
src/parser.c

File diff suppressed because it is too large Load Diff