1<?php declare(strict_types=1);
2
3namespace PhpParser;
4
5use PhpParser\Builder\Class_;
6use PhpParser\Node\Identifier;
7use PhpParser\Node\Name\FullyQualified;
8use PhpParser\Node\Scalar;
9use PhpParser\Node\Stmt;
10use PhpParser\Node\Expr;
11
12class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
13    public function testNormalizeNode(): void {
14        $builder = new Class_('SomeClass');
15        $this->assertEquals($builder->getNode(), BuilderHelpers::normalizeNode($builder));
16
17        $attribute = new Node\Attribute(new Node\Name('Test'));
18        $this->assertSame($attribute, BuilderHelpers::normalizeNode($attribute));
19
20        $this->expectException(\LogicException::class);
21        $this->expectExceptionMessage('Expected node or builder object');
22        BuilderHelpers::normalizeNode('test');
23    }
24
25    public function testNormalizeStmt(): void {
26        $stmt = new Node\Stmt\Class_('Class');
27        $this->assertSame($stmt, BuilderHelpers::normalizeStmt($stmt));
28
29        $expr = new Expr\Variable('fn');
30        $normalizedExpr = BuilderHelpers::normalizeStmt($expr);
31        $this->assertEquals(new Stmt\Expression($expr), $normalizedExpr);
32        $this->assertSame($expr, $normalizedExpr->expr);
33
34        $this->expectException(\LogicException::class);
35        $this->expectExceptionMessage('Expected statement or expression node');
36        BuilderHelpers::normalizeStmt(new Node\Attribute(new Node\Name('Test')));
37    }
38
39    public function testNormalizeStmtInvalidType(): void {
40        $this->expectException(\LogicException::class);
41        $this->expectExceptionMessage('Expected node or builder object');
42        BuilderHelpers::normalizeStmt('test');
43    }
44
45    public function testNormalizeIdentifier(): void {
46        $identifier = new Node\Identifier('fn');
47        $this->assertSame($identifier, BuilderHelpers::normalizeIdentifier($identifier));
48        $this->assertEquals($identifier, BuilderHelpers::normalizeIdentifier('fn'));
49
50        $this->expectException(\LogicException::class);
51        $this->expectExceptionMessage('Expected string or instance of Node\Identifier');
52        BuilderHelpers::normalizeIdentifier(1);
53    }
54
55    public function testNormalizeIdentifierOrExpr(): void {
56        $identifier = new Node\Identifier('fn');
57        $this->assertSame($identifier, BuilderHelpers::normalizeIdentifierOrExpr($identifier));
58
59        $expr = new Expr\Variable('fn');
60        $this->assertSame($expr, BuilderHelpers::normalizeIdentifierOrExpr($expr));
61        $this->assertEquals($identifier, BuilderHelpers::normalizeIdentifierOrExpr('fn'));
62
63        $this->expectException(\LogicException::class);
64        $this->expectExceptionMessage('Expected string or instance of Node\Identifier');
65        BuilderHelpers::normalizeIdentifierOrExpr(1);
66    }
67
68    public function testNormalizeName(): void {
69        $name = new Node\Name('test');
70        $this->assertSame($name, BuilderHelpers::normalizeName($name));
71        $this->assertEquals(
72            new Node\Name\FullyQualified(['Namespace', 'Test']),
73            BuilderHelpers::normalizeName('\\Namespace\\Test')
74        );
75        $this->assertEquals(
76            new Node\Name\Relative(['Test']),
77            BuilderHelpers::normalizeName('namespace\\Test')
78        );
79        $this->assertEquals($name, BuilderHelpers::normalizeName('test'));
80
81        $this->expectException(\LogicException::class);
82        $this->expectExceptionMessage('Name cannot be empty');
83        BuilderHelpers::normalizeName('');
84    }
85
86    public function testNormalizeNameInvalidType(): void {
87        $this->expectException(\LogicException::class);
88        $this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
89        BuilderHelpers::normalizeName(1);
90    }
91
92    public function testNormalizeNameOrExpr(): void {
93        $expr = new Expr\Variable('fn');
94        $this->assertSame($expr, BuilderHelpers::normalizeNameOrExpr($expr));
95
96        $name = new Node\Name('test');
97        $this->assertSame($name, BuilderHelpers::normalizeNameOrExpr($name));
98        $this->assertEquals(
99            new Node\Name\FullyQualified(['Namespace', 'Test']),
100            BuilderHelpers::normalizeNameOrExpr('\\Namespace\\Test')
101        );
102        $this->assertEquals(
103            new Node\Name\Relative(['Test']),
104            BuilderHelpers::normalizeNameOrExpr('namespace\\Test')
105        );
106        $this->assertEquals($name, BuilderHelpers::normalizeNameOrExpr('test'));
107
108        $this->expectException(\LogicException::class);
109        $this->expectExceptionMessage('Name cannot be empty');
110        BuilderHelpers::normalizeNameOrExpr('');
111    }
112
113    public function testNormalizeNameOrExpInvalidType(): void {
114        $this->expectException(\LogicException::class);
115        $this->expectExceptionMessage('Name must be a string or an instance of Node\Name or Node\Expr');
116        BuilderHelpers::normalizeNameOrExpr(1);
117    }
118
119    public function testNormalizeType(): void {
120        $this->assertEquals(new Node\Identifier('array'), BuilderHelpers::normalizeType('array'));
121        $this->assertEquals(new Node\Identifier('callable'), BuilderHelpers::normalizeType('callable'));
122        $this->assertEquals(new Node\Identifier('string'), BuilderHelpers::normalizeType('string'));
123        $this->assertEquals(new Node\Identifier('int'), BuilderHelpers::normalizeType('int'));
124        $this->assertEquals(new Node\Identifier('float'), BuilderHelpers::normalizeType('float'));
125        $this->assertEquals(new Node\Identifier('bool'), BuilderHelpers::normalizeType('bool'));
126        $this->assertEquals(new Node\Identifier('iterable'), BuilderHelpers::normalizeType('iterable'));
127        $this->assertEquals(new Node\Identifier('void'), BuilderHelpers::normalizeType('void'));
128        $this->assertEquals(new Node\Identifier('object'), BuilderHelpers::normalizeType('object'));
129        $this->assertEquals(new Node\Identifier('null'), BuilderHelpers::normalizeType('null'));
130        $this->assertEquals(new Node\Identifier('false'), BuilderHelpers::normalizeType('false'));
131        $this->assertEquals(new Node\Identifier('mixed'), BuilderHelpers::normalizeType('mixed'));
132        $this->assertEquals(new Node\Identifier('never'), BuilderHelpers::normalizeType('never'));
133        $this->assertEquals(new Node\Identifier('true'), BuilderHelpers::normalizeType('true'));
134
135        $intIdentifier = new Node\Identifier('int');
136        $this->assertSame($intIdentifier, BuilderHelpers::normalizeType($intIdentifier));
137
138        $intName = new Node\Name('int');
139        $this->assertSame($intName, BuilderHelpers::normalizeType($intName));
140
141        $intNullable = new Node\NullableType(new Identifier('int'));
142        $this->assertSame($intNullable, BuilderHelpers::normalizeType($intNullable));
143
144        $unionType = new Node\UnionType([new Node\Identifier('int'), new Node\Identifier('string')]);
145        $this->assertSame($unionType, BuilderHelpers::normalizeType($unionType));
146
147        $intersectionType = new Node\IntersectionType([new Node\Name('A'), new Node\Name('B')]);
148        $this->assertSame($intersectionType, BuilderHelpers::normalizeType($intersectionType));
149
150        $expectedNullable = new Node\NullableType($intIdentifier);
151        $nullable = BuilderHelpers::normalizeType('?int');
152        $this->assertEquals($expectedNullable, $nullable);
153        $this->assertEquals($intIdentifier, $nullable->type);
154
155        $this->expectException(\LogicException::class);
156        $this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
157        BuilderHelpers::normalizeType(1);
158    }
159
160    public function testNormalizeTypeNullableVoid(): void {
161        $this->expectException(\LogicException::class);
162        $this->expectExceptionMessage('void type cannot be nullable');
163        BuilderHelpers::normalizeType('?void');
164    }
165
166    public function testNormalizeTypeNullableMixed(): void {
167        $this->expectException(\LogicException::class);
168        $this->expectExceptionMessage('mixed type cannot be nullable');
169        BuilderHelpers::normalizeType('?mixed');
170    }
171
172    public function testNormalizeTypeNullableNever(): void {
173        $this->expectException(\LogicException::class);
174        $this->expectExceptionMessage('never type cannot be nullable');
175        BuilderHelpers::normalizeType('?never');
176    }
177
178    public function testNormalizeValue(): void {
179        $expression = new Scalar\Int_(1);
180        $this->assertSame($expression, BuilderHelpers::normalizeValue($expression));
181
182        $this->assertEquals(new Expr\ConstFetch(new Node\Name('null')), BuilderHelpers::normalizeValue(null));
183        $this->assertEquals(new Expr\ConstFetch(new Node\Name('true')), BuilderHelpers::normalizeValue(true));
184        $this->assertEquals(new Expr\ConstFetch(new Node\Name('false')), BuilderHelpers::normalizeValue(false));
185        $this->assertEquals(new Scalar\Int_(2), BuilderHelpers::normalizeValue(2));
186        $this->assertEquals(new Scalar\Float_(2.5), BuilderHelpers::normalizeValue(2.5));
187        $this->assertEquals(new Scalar\String_('text'), BuilderHelpers::normalizeValue('text'));
188        $this->assertEquals(
189            new Expr\Array_([
190                new Node\ArrayItem(new Scalar\Int_(0)),
191                new Node\ArrayItem(new Scalar\Int_(1), new Scalar\String_('test')),
192            ]),
193            BuilderHelpers::normalizeValue([
194                0,
195                'test' => 1,
196            ])
197        );
198
199        $this->expectException(\LogicException::class);
200        $this->expectExceptionMessage('Invalid value');
201        BuilderHelpers::normalizeValue(new \stdClass());
202    }
203
204    public function testNormalizeDocComment(): void {
205        $docComment = new Comment\Doc('Some doc comment');
206        $this->assertSame($docComment, BuilderHelpers::normalizeDocComment($docComment));
207
208        $this->assertEquals($docComment, BuilderHelpers::normalizeDocComment('Some doc comment'));
209
210        $this->expectException(\LogicException::class);
211        $this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
212        BuilderHelpers::normalizeDocComment(1);
213    }
214
215    public function testNormalizeAttribute(): void {
216        $attribute = new Node\Attribute(new Node\Name('Test'));
217        $attributeGroup = new Node\AttributeGroup([$attribute]);
218
219        $this->assertEquals($attributeGroup, BuilderHelpers::normalizeAttribute($attribute));
220        $this->assertSame($attributeGroup, BuilderHelpers::normalizeAttribute($attributeGroup));
221
222        $this->expectException(\LogicException::class);
223        $this->expectExceptionMessage('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
224        BuilderHelpers::normalizeAttribute('test');
225    }
226
227    public function testNormalizeValueEnum() {
228        if (\PHP_VERSION_ID <= 80100) {
229            $this->markTestSkipped('Enums are supported since PHP 8.1');
230        }
231
232        include __DIR__ . '/../fixtures/Suit.php';
233
234        $this->assertEquals(new Expr\ClassConstFetch(new FullyQualified(\Suit::class), new Identifier('Hearts')), BuilderHelpers::normalizeValue(\Suit::Hearts));
235    }
236}
237