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