Guides

Building IDE Tools with Forte

Use Forte's position tracking, error recovery, and rewriting APIs to build cursor-aware lookups, diagnostic reporting, and automated quick fixes for Blade language tools.

#What You Can Build

Forte gives you the building blocks for a wide range of IDE and developer tooling:

  • Linters: validate templates against custom rules
  • Formatters: normalize whitespace, attribute order, or class lists
  • Refactoring tools: rename components, migrate directives, restructure templates
  • Code analysis: detect unused slots, count directive usage, find accessibility gaps
  • Diagnostic reporters: surface parse errors with line context and highlighting
  • Completion providers: suggest directives, component names, and attributes
  • Go-to-definition: resolve component references and directive sources

#Finding the Node Under the Cursor

The starting point for most cursor-aware features is findNodeAtOffset or findNodeAtPosition. Both return the most deeply nested node at the given location:

<?php
use Forte\Ast\Elements\ElementNode;
use Forte\Facades\Forte;
$doc = Forte::parse('<div><span class="title">Hello</span></div>');
// Find the node at byte offset 26 (inside the text "Hello")
$node = $doc->findNodeAtOffset(26);
$node->isText(); // true
// Walk up to find the containing element
$parent = $node->getParent();
$parent->tagNameText(); // "span"

Once you have the deepest node, walk up with getParent() to find the relevant context. For example, a hover provider might walk up until it finds an ElementNode or ComponentNode to display tag-level information, while a completion provider might check whether the cursor is inside a directive's arguments.

<?php
use Forte\Ast\Elements\ElementNode;
$node = $doc->findNodeAtOffset($cursorOffset);
// Walk up to the nearest element
$element = $node->closestOfType(ElementNode::class);
if ($element) {
$element->tagNameText(); // which element contains the cursor
$element->getAttributes(); // attributes available for inspection
}

#Reporting Errors with Context

Forte recovers from malformed syntax and records diagnostics instead of throwing exceptions. To build a diagnostic reporter, combine diagnostics() with getLineExcerpt() for contextual output:

<?php
use Forte\Facades\Forte;
$template = <<<'BLADE'
<div>
<p>Hello</p>
{{ $unclosed
<span>World</span>
<p>More content</p>
</div>
BLADE;
$doc = Forte::parse($template);
$doc->hasErrors(); // true
$doc->diagnostics()->count(); // at least 1
$excerpt = $doc->getLineExcerpt(3, 1);
count($excerpt); // 3 (the target line plus 1 line of context on each side)

#Building a Diagnostic Reporter

Here is a small working example that parses a Blade template, collects diagnostics, and formats each error with file path, line number, column, message, and source context:

<?php
use Forte\Facades\Forte;
function reportErrors(string $filePath): string
{
$doc = Forte::parseFile($filePath);
if (! $doc->hasErrors()) {
return 'No errors found.';
}
$output = [];
$source = $doc->source();
foreach ($doc->diagnostics()->errors() as $diag) {
$line = $doc->getLineForOffset($diag->start);
$excerpt = $doc->getLineExcerpt($line, 2);
$output[] = sprintf(
"%s:%d -- %s",
$filePath,
$line,
$diag->message
);
foreach ($excerpt as $num => $text) {
$marker = ($num === $line) ? ' >> ' : ' ';
$output[] = sprintf('%s%4d | %s', $marker, $num, $text);
}
$output[] = '';
}
return implode("\n", $output);
}

Parse the template, iterate over diagnostics()->errors(), convert each diagnostic's start offset to a line number with getLineForOffset, and use getLineExcerpt to display surrounding context.

#Component and Directive Discovery

When building completion providers, you need to know which directives and components are available. Forte exposes this through its registries:

<?php
use Forte\Facades\Forte;
// Iterate registered directive names for autocomplete suggestions
$directives = Forte::directives();
foreach ($directives->allDirectives() as $name => $registered) {
// suggest $name as a directive completion
}
// Discover registered component prefixes
$prefixes = Forte::components()->getPrefixes(); // ['x-', 'livewire:', ...]

These registries reflect the current application state, including any directives registered by packages or custom directive definitions. Use them to build accurate completion lists without hardcoding directive names.

#Quick Fixes with the Selection API

The Selection API is well suited for implementing automated quick fixes. Each fix is a small rewrite() call that targets specific patterns and applies corrections.

Finding <img> tags without alt attributes and adding a placeholder:

<?php
use Forte\Facades\Forte;
use Forte\Rewriting\RewriteBuilder;
$doc = Forte::parse('<img src="hero.jpg"><img src="logo.png" alt="Logo">');
$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
$builder->findAll('img')
->filter(fn ($node) => ! $node->hasAttribute('alt'))
->setAttribute('alt', '');
});
$newDoc->render(); // '<img src="hero.jpg" alt=""><img src="logo.png" alt="Logo">'

Removing deprecated directives from templates:

<?php
use Forte\Facades\Forte;
use Forte\Rewriting\Passes\Directives\RemoveDirective;
$doc = Forte::parse('<div>@deprecated("reason")<p>content</p></div>');
$fixed = $doc->apply(new RemoveDirective('deprecated'));

You can compose multiple quick fixes into a pipeline for batch application:

<?php
use Forte\Rewriting\Passes\Elements\SetAttribute;
use Forte\Rewriting\Passes\Elements\AddClass;
use Forte\Rewriting\RewritePipeline;
$fixes = new RewritePipeline(
new SetAttribute('img', 'loading', 'lazy'),
new AddClass('a', 'link'),
);
$fixed = $fixes->rewrite($doc);

#See Also

Continue with these related Forte guides: