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