packages/sql-formatter/src/Core/Compact/Document.php
1<?php
2
3declare(strict_types=1);
4
5namespace SqlFormatter\Core\Compact;
6
7use SqlParser\Lexer\Token;
8use SqlParser\Parser\Node;
9
10/**
11 * Builds a canonical token stream without changing the parser's immutable tree.
12 *
13 * @visibility SqlFormatter
14 */
15final class Document
16{
17 /**
18 * @var list<Token> Canonical tokens in source order
19 */
20 public readonly array $tokens;
21 /**
22 * Retained directives after the last SQL token.
23 */
24 public readonly string $trailing;
25 /**
26 * @var list<list<int>> Grammar-owned reduction candidates
27 */
28 public readonly array $candidates;
29 private readonly ?string $shape;
30
31 /**
32 * Preserves directive ownership before applying grammar-specific reductions.
33 */
34 public function __construct(Node $tree, Settings $settings)
35 {
36 $trivia = new Trivia($settings->nesting);
37 $cleaned = [];
38 $protected = [];
39 $verbatim = [];
40 $trailing = '';
41 foreach ($tree->tokens() as $token) {
42 $leading = $trivia->clean($token->leading);
43 if ($trivia->executable) {
44 $verbatim[$token->offset] = true;
45 }
46 if ($trivia->executable || $leading !== '') {
47 $protected[$token->offset] = true;
48 }
49 if ($token->text === '') {
50 $trailing .= $leading;
51 }
52 $cleaned[$token->offset] = new Token($token->symbol, $token->name, $token->text, $token->offset, $leading);
53 }
54 $this->trailing = $trailing . $trivia->clean($tree->trailing);
55 $tokens = (new Visitor($cleaned, $protected, $verbatim, $settings))->tokens($tree);
56 $last = $tokens[count($tokens) - 1] ?? null;
57 while ($last?->text === ';' && $this->trailing === '' && !isset($protected[$last->offset])) {
58 array_pop($tokens);
59 $last = $tokens[count($tokens) - 1] ?? null;
60 }
61 $this->tokens = $tokens;
62 $this->candidates = (new Reductions($settings->grouping, $settings->aliases))->candidates($tree, $protected);
63 $grouping = array_fill_keys(array_merge([], ...$this->candidates), true);
64 $this->shape = (new Shape(array_column($tokens, null, 'offset'), $grouping))->of($tree);
65 }
66
67 /**
68 * Verifies canonical terminal kinds, spellings, and retained directives.
69 */
70 public function signature(): string
71 {
72 return serialize([$this->shape, $this->trailing]);
73 }
74}
75