You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Render zero iterations when an f-repeat list binding is absent while preserving errors for present non-array values. Update Rust and Node tests plus docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: crates/microsoft-fast-build/DESIGN.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -152,7 +152,7 @@ Both return `RenderError::EmptyBinding` for blank expressions and `RenderError::
152
152
Callers decide how to handle unresolved values:
153
153
- Content bindings (`{{expr}}` / `{{{expr}}}`) render an empty string.
154
154
- HTML attribute bindings omit the entire attribute.
155
-
-`<f-repeat>`still requires its list binding to resolve to an array; a missing list binding returns `RenderError::MissingState`.
155
+
-`<f-repeat>`treats a missing list binding as an empty array and renders zero iterations; present non-array values return `RenderError::NotAnArray`.
156
156
-`<f-when>` evaluates a missing binding as falsy.
157
157
158
158
### Loop variable scoping
@@ -198,7 +198,7 @@ Because `||` is sought before `&&`, the recursive split on `||` runs first. Each
198
198
199
199
1. Extracts inner HTML and end position (same as `render_when`).
200
200
2. Parses `value="{{item in items}}"` with `parse_repeat_expr` — expects exactly three whitespace-separated tokens where the middle is `"in"`.
201
-
3. Resolves the list expression. Returns `RenderError::NotAnArray` if the value is not a `JsonValue::Array`.
201
+
3. Resolves the list expression. Missing values are treated as an empty array. Present non-array values return `RenderError::NotAnArray`.
202
202
4. For each item in the array, pushes `(var_name, item)` onto a new `loop_vars` vec and calls `render_node` on the inner template.
203
203
5. Uses `Iterator::collect::<Result<String, _>>()` to short-circuit on the first error in any iteration.
204
204
@@ -557,6 +557,6 @@ A hand-rolled recursive-descent parser. No external crates.
557
557
558
558
**Atomic tag processing for attribute bindings.** When a plain HTML opening tag in the literal region contains `{{expr}}` attribute values, those values are resolved and `data-fe-c` is injected into the tag as a whole before `next_directive` ever sees them. This prevents the `{{expr}}` inside attributes from being mistaken for content bindings. The cost is that `next_directive` is called once extra per tag iteration, but tags are short and rare enough that this has no meaningful performance impact.
559
559
560
-
**`Result` throughout.** All render functions return `Result<_, RenderError>`. Errors propagate via `?` for malformed templates, invalid JSON, invalid repeat expressions, and missing/invalid required directive state. Missing optional values are handled by binding context: content bindings render empty output, attribute bindings omit the attribute, `<f-when>` treats the value as falsy, and `<f-repeat>`still errors when its list binding is missing or not an array.
560
+
**`Result` throughout.** All render functions return `Result<_, RenderError>`. Errors propagate via `?` for malformed templates, invalid JSON, invalid repeat expressions, and invalid directive state. Missing optional values are handled by binding context: content bindings render empty output, attribute bindings omit the attribute, `<f-when>` treats the value as falsy, and `<f-repeat>`treats a missing list binding as an empty array while still erroring when a present value is not an array.
561
561
562
562
**Left-to-right, first-match scanning.** Directives are found by searching for their literal opening strings. The earliest position wins. This is O(n×d) where n is the template length and d is the number of directive types — acceptable for the template sizes this crate targets.
Copy file name to clipboardExpand all lines: crates/microsoft-fast-build/README.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -221,6 +221,7 @@ When `value` is a bare reference (e.g. `{{items}}`), the value is coerced to a b
221
221
```
222
222
223
223
Inside `<f-repeat>`, `{{item}}` resolves to the current loop variable and `{{$index}}` resolves to the 0-based iteration index. Other bindings fall back to the root state (e.g. `{{title}}` above).
224
+
If the list binding or dot path is missing, `<f-repeat>` treats it as an empty array and renders zero iterations. Present non-array values still return `RenderError::NotAnArray`.
224
225
225
226
```html
226
227
<f-repeatvalue="{{row in rows}}">
@@ -594,7 +595,7 @@ All render functions return `Result<String, RenderError>`. `RenderError` is an e
594
595
|`UnclosedBinding`|`{{` with no closing `}}`|
595
596
|`UnclosedUnescapedBinding`|`{{{` with no closing `}}}`|
596
597
|`EmptyBinding`|`{{}}` — blank expression |
597
-
|`MissingState`|`<f-repeat>`list binding is absent from state|
598
+
|`MissingState`|Required directive state is absent; missing `<f-repeat>`lists render zero iterations instead|
598
599
|`UnclosedDirective`|`<f-when>` / `<f-repeat>` with no matching close tag |
- Attribute bindings omit the entire attribute, including unresolved dot paths: `<div class="{{foo.bar}}"></div>` becomes `<div></div>`.
92
-
-`<f-repeat>`still requires its list binding to resolve to an array and errors when the list is missing or not an array.
92
+
-`<f-repeat>`treats a missing list binding, including unresolved dot paths, as an empty array and renders zero iterations. If the binding is present but is not an array, rendering still errors.
0 commit comments