packages/sql-faker/tests/Unit/Compiler/Bison/Lexer/ActionScannerTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Compiler\Bison\Lexer;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Compiler\Bison\Lexer\ActionScanner;
11use SqlFaker\Compiler\Bison\Lexer\BisonLexeme;
12use SqlFaker\Compiler\Bison\Lexer\BisonToken;
13use SqlFaker\Compiler\Bison\Lexer\BisonTrivia;
14use SqlFaker\Compiler\Bison\Lexer\SourceCursor;
15
16#[CoversClass(ActionScanner::class)]
17#[UsesClass(BisonLexeme::class)]
18#[UsesClass(BisonToken::class)]
19#[UsesClass(BisonTrivia::class)]
20#[UsesClass(SourceCursor::class)]
21#[UsesClass(\SqlFaker\Compiler\GrammarParseException::class)]
22final class ActionScannerTest extends TestCase
23{
24    public function testHandlesClaimsOnlyAnOpeningBrace(): void
25    {
26        $scanner = new ActionScanner();
27
28        self::assertTrue($scanner->handles('{'));
29        self::assertFalse($scanner->handles('}'));
30    }
31
32    public function testScanExcludesTheOuterBracesFromTheBody(): void
33    {
34        $cursor = new SourceCursor('{ $$ = $1; } rest');
35
36        $token = (new ActionScanner())->scan($cursor);
37
38        self::assertSame(BisonLexeme::Action, $token->type);
39        self::assertSame(' $$ = $1; ', $token->value);
40        self::assertSame(0, $token->offset);
41        self::assertSame(' rest', $cursor->takeRest());
42    }
43
44    public function testScanReadsAnEmptyActionAsAnEmptyBody(): void
45    {
46        $cursor = new SourceCursor('{}');
47
48        self::assertSame('', (new ActionScanner())->scan($cursor)->value);
49    }
50
51    public function testScanKeepsNestedBracesInTheBody(): void
52    {
53        $cursor = new SourceCursor('{ if (x) { y(); } } rest');
54
55        self::assertSame(' if (x) { y(); } ', (new ActionScanner())->scan($cursor)->value);
56        self::assertSame(' rest', $cursor->takeRest());
57    }
58
59    public function testScanIgnoresABraceInsideAString(): void
60    {
61        $cursor = new SourceCursor('{ printf("}"); } rest');
62
63        self::assertSame(' printf("}"); ', (new ActionScanner())->scan($cursor)->value);
64        self::assertSame(' rest', $cursor->takeRest());
65    }
66
67    public function testScanIgnoresABraceInsideACharacterLiteral(): void
68    {
69        $cursor = new SourceCursor("{ c = '}'; } rest");
70
71        self::assertSame(" c = '}'; ", (new ActionScanner())->scan($cursor)->value);
72        self::assertSame(' rest', $cursor->takeRest());
73    }
74
75    public function testScanIgnoresABraceInsideALineComment(): void
76    {
77        $cursor = new SourceCursor("{ // }\n x; } rest");
78
79        self::assertSame(" // }\n x; ", (new ActionScanner())->scan($cursor)->value);
80        self::assertSame(' rest', $cursor->takeRest());
81    }
82
83    public function testScanIgnoresABraceInsideABlockComment(): void
84    {
85        $cursor = new SourceCursor('{ /* } */ x; } rest');
86
87        self::assertSame(' /* } */ x; ', (new ActionScanner())->scan($cursor)->value);
88        self::assertSame(' rest', $cursor->takeRest());
89    }
90
91    public function testScanReadsALoneSlashAsBodyText(): void
92    {
93        $cursor = new SourceCursor('{ a / b } rest');
94
95        self::assertSame(' a / b ', (new ActionScanner())->scan($cursor)->value);
96    }
97
98    public function testScanRunsAnUnclosedActionToTheEndOfTheFile(): void
99    {
100        $cursor = new SourceCursor('{ never closed');
101
102        self::assertSame(' never closed', (new ActionScanner())->scan($cursor)->value);
103        self::assertTrue($cursor->atEnd());
104    }
105}
106