packages/sql-formatter/src/Core/Layout/Headers.php

1<?php
2
3declare(strict_types=1);
4
5namespace SqlFormatter\Core\Layout;
6
7use SqlFormatter\Core\Syntax\Document;
8
9/**
10 * Measures and emits headers without looking inside nested query blocks.
11 *
12 * @visibility SqlFormatter
13 */
14final class Headers
15{
16    /**
17     * Reads annotations from the concrete syntax tree.
18     */
19    public function __construct(private readonly Document $document)
20    {
21    }
22
23    /**
24     * Finds the widest header at this parenthesis level.
25     */
26    public function width(int $start, int $end): int
27    {
28        $width = 0;
29        for ($index = $start; $index <= $end; $index++) {
30            $close = $this->document->pairs[$index] ?? $this->document->casePairs[$index] ?? null;
31            if ($close !== null) {
32                $index = $close;
33                continue;
34            }
35            $last = $this->document->clauses[$index] ?? null;
36            if ($last !== null) {
37                $width = max($width, $this->length($index, $last));
38            }
39        }
40        return $width;
41    }
42
43    /**
44     * Measures a header with one space between its words.
45     */
46    public function length(int $start, int $end): int
47    {
48        $width = $end - $start;
49        for ($index = $start; $index <= $end; $index++) {
50            $width += strlen($this->document->tokens[$index]->text);
51        }
52        return $width;
53    }
54
55    /**
56     * Emits a complete multiword header, keeping interleaved comments.
57     */
58    public function write(Writer $writer, int $start, int $end, Policy $policy): void
59    {
60        for ($index = $start; $index <= $end; $index++) {
61            $writer->token($this->document->tokens[$index], $index === $start ? $policy->beforeHeader($this->length($start, $end)) : ' ');
62        }
63    }
64}
65