Skip to content

Commit f36bef6

Browse files
committed
Inject $loop variable in foreach and forelse blocks
1 parent 74b6332 commit f36bef6

2 files changed

Lines changed: 27 additions & 18 deletions

File tree

docs/todo/blade.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,6 @@ of the file rather than inside a `@php` / `<?php` block. All code
6060
actions that produce text edits need their ranges translated, and
6161
actions that generate new code need to be aware of Blade structure.
6262

63-
Remaining work in this phase:
64-
65-
### `$loop` variable injection
66-
67-
The preprocessor injects `$errors` and `$__env` in the prologue, but
68-
does not yet inject a `$loop` variable inside `@foreach` blocks:
69-
70-
```php
71-
/** @var object{index: int, iteration: int, remaining: int, count: int, first: bool, last: bool, even: bool, odd: bool, depth: int, parent: ?object} $loop */
72-
$loop = (object)[];
73-
```
74-
75-
### Additional test coverage
76-
77-
- `$loop->` inside a `@foreach` (blocked on `$loop` variable injection)
78-
7963
---
8064

8165
## Phase 2: Component Support

src/blade/preprocessor.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,13 @@ pub fn preprocess(content: &str) -> (String, BladeSourceMap) {
162162
replacement = " if (true): ".to_string();
163163
next_mode = Mode::Html;
164164
}
165+
} else if matches!(directive, "foreach" | "forelse") {
166+
replacement = format!(" {} ", translate_directive(directive));
167+
next_mode = Mode::DirectiveArgs(": /** @var object{index: int, iteration: int, remaining: int, count: int, first: bool, last: bool, even: bool, odd: bool, depth: int, parent: ?object} $loop */ $loop = (object)[];");
168+
paren_depth = 0;
165169
} else if matches!(
166170
directive,
167171
"if" | "elseif"
168-
| "foreach"
169-
| "forelse"
170172
| "for"
171173
| "while"
172174
| "switch"
@@ -446,6 +448,29 @@ mod tests {
446448
);
447449
}
448450

451+
#[test]
452+
fn test_preprocess_foreach_loop_variable() {
453+
let content = "@foreach($items as $item)\n{{ $loop->first }}\n@endforeach\n";
454+
let (php, _) = preprocess(content);
455+
assert!(php.contains("$loop"), "should inject $loop variable: {}", php);
456+
assert!(
457+
php.contains("object{index: int"),
458+
"should have typed $loop: {}",
459+
php
460+
);
461+
// $loop should be declared before its usage
462+
let loop_decl = php.find("$loop = (object)[];").unwrap();
463+
let loop_use = php.rfind("$loop").unwrap();
464+
assert!(loop_use > loop_decl, "$loop usage after declaration: {}", php);
465+
}
466+
467+
#[test]
468+
fn test_preprocess_forelse_loop_variable() {
469+
let content = "@forelse($items as $item)\n{{ $loop->index }}\n@empty\n@endforelse\n";
470+
let (php, _) = preprocess(content);
471+
assert!(php.contains("$loop = (object)[];"), "forelse should also inject $loop: {}", php);
472+
}
473+
449474
#[test]
450475
fn test_preprocess_echo_with_string_braces() {
451476
let content = "{{ \"}} \" }}";

0 commit comments

Comments
 (0)