Laravel Blade templates are a unique formatting challenge. A single .blade.php file can contain all three of:
- Standard HTML tags (
<div>,<p>,<section>) - PHP code blocks (
<?php ... ?>,{{ $variable }}) - Blade directives (
@if,@foreach,@section, etc.)
A formatter needs to track indent changes from all three sources simultaneously and combine them correctly.
The directive pair engine
Blade directives come in opening/closing pairs. The formatter maps them to indent changes using three regex patterns:
// These directives CLOSE a block (reduce indent before printing this line): const decreaseRegex = /^@(endif|endforeach|endfor|endwhile|endsection|endauth|endguest|endpush|endcan|endunless|endverbatim|endproduction|endenv|endonce)\b/; // These directives OPEN a block (increase indent after printing this line): const increaseRegex = /^@(if|foreach|for|while|section|auth|guest|push|can|unless|verbatim|production|env|once|hasSection)\b/; // These directives are mid-block (dedent BEFORE printing, maintain same level after): const bothRegex = /^@(else|elseif|empty)\b/;
The "both" case: @else, @elseif, @empty
Mid-block directives are the interesting edge case. Consider:
@if($user->isAdmin()) <div>Admin Panel</div> @else <div>User Panel</div> @endif
@else should be printed at the same indent level as @if (one level in from its container), even though it's inside the @if block. Then the content after @else should be indented one level again.
The formatter handles this with isLeadingClosingBladeDirective() — which returns true for both @end* directives AND @else/@elseif/@empty. When this function returns true, the print indent is reduced by 1 before the line is output. After printing, the indent level stays the same (the bothRegex returns 0 from getBladeDirectiveIndentChange).
Combining Blade and HTML changes
The handleBladeFormatting() function computes both the HTML tag change and the Blade directive change, then applies them together:
function handleBladeFormatting(line, formattedLines, spaces, currentIndent) {
const htmlChange = getHTMLIndentChange(line); // from HTML tags
const leadingHtmlClose = getLeadingClosingTagsCount(line); // leading </tags>
const bladeChange = getBladeDirectiveIndentChange(line); // from @directives
const isLeadingBladeClose = isLeadingClosingBladeDirective(line);
let printCloseCount = leadingHtmlClose;
if (isLeadingBladeClose) printCloseCount += 1;
// Print at reduced indent (closing tags + closing directives dedent before print)
const printIndent = Math.max(0, currentIndent - printCloseCount);
formattedLines.push(' '.repeat(printIndent * spaces) + line);
// Next line's indent combines both HTML and Blade changes
currentIndent = Math.max(0, currentIndent + htmlChange + bladeChange);
return currentIndent;
}This means a line like @endif</div> (closing a Blade block and an HTML tag on the same line) correctly dedents twice before printing and then reduces the running indent by 2 for subsequent lines.
---
