packages/sql-catalog/tests/Unit/Core/Php/DeclaredGlobalsTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Php;
6
7use PhpParser\Node\Stmt\Global_;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\DataProvider;
10use PHPUnit\Framework\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use SqlCatalog\Core\Php\DeclaredGlobals;
13use SqlCatalog\Core\Php\ParsedFile;
14use SqlCatalog\Core\Php\SourceParser;
15
16#[CoversClass(DeclaredGlobals::class)]
17#[UsesClass(ParsedFile::class)]
18#[UsesClass(SourceParser::class)]
19final class DeclaredGlobalsTest extends TestCase
20{
21 public function testClassOfNamesAGlobalAnExtensionDeclares(): void
22 {
23 $file = (new SourceParser())->parse('a.php', '<?php function f() { global $wpdb; }');
24 $statement = $file->statements[0];
25 self::assertInstanceOf(\PhpParser\Node\Stmt\Function_::class, $statement);
26 $declaration = $statement->stmts[0];
27 self::assertInstanceOf(Global_::class, $declaration);
28
29 self::assertSame('wpdb', (new DeclaredGlobals(['wpdb' => 'wpdb']))->classOf($declaration, 'wpdb'));
30 }
31
32 public function testClassOfIsSilentWhenNothingSaysWhatTheGlobalHolds(): void
33 {
34 $file = (new SourceParser())->parse('a.php', '<?php function f() { global $other; }');
35 $statement = $file->statements[0];
36 self::assertInstanceOf(\PhpParser\Node\Stmt\Function_::class, $statement);
37 $declaration = $statement->stmts[0];
38 self::assertInstanceOf(Global_::class, $declaration);
39
40 self::assertNull((new DeclaredGlobals())->classOf($declaration, 'other'));
41 }
42
43 public function testDeclaredNamesTheClassAnExtensionGivesAGlobal(): void
44 {
45 $globals = new DeclaredGlobals(['wpdb' => 'App\\Db', 'cache' => 'App\\Cache']);
46
47 self::assertSame('App\\Db', $globals->declared('wpdb'));
48 self::assertSame('App\\Cache', $globals->declared('cache'));
49 }
50
51 public function testDeclaredIsSilentForAGlobalNoExtensionDeclares(): void
52 {
53 self::assertNull((new DeclaredGlobals(['wpdb' => 'App\\Db']))->declared('other'));
54 self::assertNull((new DeclaredGlobals(['wpdb' => 'App\\Db']))->declared('$wpdb'));
55 self::assertNull((new DeclaredGlobals())->declared('wpdb'));
56 }
57
58 public function testDocumentedReadsTheTagOnTheEnclosingDeclaration(): void
59 {
60 $file = (new SourceParser())->parse(
61 'a.php',
62 '<?php' . "\n" . '/** @global \\App\\Db $db */' . "\n" . 'function f() { global $db; }',
63 );
64 $statement = $file->statements[0];
65 self::assertInstanceOf(\PhpParser\Node\Stmt\Function_::class, $statement);
66 $declaration = $statement->stmts[0];
67 self::assertInstanceOf(Global_::class, $declaration);
68
69 self::assertSame('App\\Db', (new DeclaredGlobals())->documented($declaration, 'db'));
70 }
71
72 public function testDocumentedIsSilentWhenNoTagNamesTheVariable(): void
73 {
74 $file = (new SourceParser())->parse(
75 'a.php',
76 '<?php' . "\n" . '/** @global wpdb $wpdb */' . "\n" . 'function f() { global $db; }',
77 );
78 $statement = $file->statements[0];
79 self::assertInstanceOf(\PhpParser\Node\Stmt\Function_::class, $statement);
80 $declaration = $statement->stmts[0];
81 self::assertInstanceOf(Global_::class, $declaration);
82
83 self::assertNull((new DeclaredGlobals())->documented($declaration, 'db'));
84 }
85
86 public function testCommentsClimbToTheDeclarationTheGlobalIsWrittenIn(): void
87 {
88 $file = (new SourceParser())->parse(
89 'a.php',
90 '<?php' . "\n" . '/** @global wpdb $wpdb */' . "\n" . 'function f() { /** @var wpdb $wpdb */ global $wpdb; }',
91 );
92 $statement = $file->statements[0];
93 self::assertInstanceOf(\PhpParser\Node\Stmt\Function_::class, $statement);
94 $declaration = $statement->stmts[0];
95 self::assertInstanceOf(Global_::class, $declaration);
96
97 self::assertSame(
98 ['/** @var wpdb $wpdb */', '/** @global wpdb $wpdb */'],
99 (new DeclaredGlobals())->comments($declaration),
100 );
101 }
102
103 /**
104 * @return list<array{string, string, string|null}>
105 */
106 public static function providerTagged(): array
107 {
108 return [
109 ['/** @global wpdb $wpdb */', 'wpdb', 'wpdb'],
110 ['/** @var \\App\\Db $db */', 'db', 'App\\Db'],
111 ['/** @global wpdb|null $wpdb */', 'wpdb', 'wpdb'],
112 ['/** @global wpdb $wpdb */', 'other', null],
113 ['/** nothing */', 'wpdb', null],
114 ['/** @global wpdb $wpdbx */', 'wpdb', null],
115 ];
116 }
117
118 #[DataProvider('providerTagged')]
119 public function testTaggedReadsTheClassOutOfADocComment(string $comment, string $name, ?string $expected): void
120 {
121 self::assertSame($expected, (new DeclaredGlobals())->tagged($comment, $name));
122 }
123}
124