1<?php declare(strict_types=1);
2
3namespace PhpParser\Builder;
4
5use PhpParser\Modifiers;
6use PhpParser\Node;
7use PhpParser\Node\Arg;
8use PhpParser\Node\Attribute;
9use PhpParser\Node\AttributeGroup;
10use PhpParser\Node\Expr;
11use PhpParser\Node\Identifier;
12use PhpParser\Node\Name;
13use PhpParser\Node\Scalar;
14use PhpParser\Node\Scalar\Int_;
15
16class ParamTest extends \PHPUnit\Framework\TestCase {
17    public function createParamBuilder($name) {
18        return new Param($name);
19    }
20
21    /**
22     * @dataProvider provideTestDefaultValues
23     */
24    public function testDefaultValues($value, $expectedValueNode): void {
25        $node = $this->createParamBuilder('test')
26            ->setDefault($value)
27            ->getNode()
28        ;
29
30        $this->assertEquals($expectedValueNode, $node->default);
31    }
32
33    public static function provideTestDefaultValues() {
34        return [
35            [
36                null,
37                new Expr\ConstFetch(new Node\Name('null'))
38            ],
39            [
40                true,
41                new Expr\ConstFetch(new Node\Name('true'))
42            ],
43            [
44                false,
45                new Expr\ConstFetch(new Node\Name('false'))
46            ],
47            [
48                31415,
49                new Scalar\Int_(31415)
50            ],
51            [
52                3.1415,
53                new Scalar\Float_(3.1415)
54            ],
55            [
56                'Hallo World',
57                new Scalar\String_('Hallo World')
58            ],
59            [
60                [1, 2, 3],
61                new Expr\Array_([
62                    new Node\ArrayItem(new Scalar\Int_(1)),
63                    new Node\ArrayItem(new Scalar\Int_(2)),
64                    new Node\ArrayItem(new Scalar\Int_(3)),
65                ])
66            ],
67            [
68                ['foo' => 'bar', 'bar' => 'foo'],
69                new Expr\Array_([
70                    new Node\ArrayItem(
71                        new Scalar\String_('bar'),
72                        new Scalar\String_('foo')
73                    ),
74                    new Node\ArrayItem(
75                        new Scalar\String_('foo'),
76                        new Scalar\String_('bar')
77                    ),
78                ])
79            ],
80            [
81                new Scalar\MagicConst\Dir(),
82                new Scalar\MagicConst\Dir()
83            ]
84        ];
85    }
86
87    /**
88     * @dataProvider provideTestTypes
89     * @dataProvider provideTestNullableTypes
90     * @dataProvider provideTestUnionTypes
91     */
92    public function testTypes($typeHint, $expectedType): void {
93        $node = $this->createParamBuilder('test')
94            ->setType($typeHint)
95            ->getNode()
96        ;
97        $type = $node->type;
98
99        /* Manually implement comparison to avoid __toString stupidity */
100        if ($expectedType instanceof Node\NullableType) {
101            $this->assertInstanceOf(get_class($expectedType), $type);
102            $expectedType = $expectedType->type;
103            $type = $type->type;
104        }
105
106        $this->assertInstanceOf(get_class($expectedType), $type);
107        $this->assertEquals($expectedType, $type);
108    }
109
110    public static function provideTestTypes() {
111        return [
112            ['array', new Node\Identifier('array')],
113            ['callable', new Node\Identifier('callable')],
114            ['bool', new Node\Identifier('bool')],
115            ['int', new Node\Identifier('int')],
116            ['float', new Node\Identifier('float')],
117            ['string', new Node\Identifier('string')],
118            ['iterable', new Node\Identifier('iterable')],
119            ['object', new Node\Identifier('object')],
120            ['Array', new Node\Identifier('array')],
121            ['CALLABLE', new Node\Identifier('callable')],
122            ['mixed', new Node\Identifier('mixed')],
123            ['Some\Class', new Node\Name('Some\Class')],
124            ['\Foo', new Node\Name\FullyQualified('Foo')],
125            ['self', new Node\Name('self')],
126            [new Node\Name('Some\Class'), new Node\Name('Some\Class')],
127        ];
128    }
129
130    public static function provideTestNullableTypes() {
131        return [
132            ['?array', new Node\NullableType(new Node\Identifier('array'))],
133            ['?Some\Class', new Node\NullableType(new Node\Name('Some\Class'))],
134            [
135                new Node\NullableType(new Node\Identifier('int')),
136                new Node\NullableType(new Node\Identifier('int'))
137            ],
138            [
139                new Node\NullableType(new Node\Name('Some\Class')),
140                new Node\NullableType(new Node\Name('Some\Class'))
141            ],
142        ];
143    }
144
145    public static function provideTestUnionTypes() {
146        return [
147            [
148                new Node\UnionType([
149                    new Node\Name('Some\Class'),
150                    new Node\Identifier('array'),
151                ]),
152                new Node\UnionType([
153                    new Node\Name('Some\Class'),
154                    new Node\Identifier('array'),
155                ]),
156            ],
157            [
158                new Node\UnionType([
159                    new Node\Identifier('self'),
160                    new Node\Identifier('array'),
161                    new Node\Name\FullyQualified('Foo')
162                ]),
163                new Node\UnionType([
164                    new Node\Identifier('self'),
165                    new Node\Identifier('array'),
166                    new Node\Name\FullyQualified('Foo')
167                ]),
168            ],
169        ];
170    }
171
172    public function testVoidTypeError(): void {
173        $this->expectException(\LogicException::class);
174        $this->expectExceptionMessage('Parameter type cannot be void');
175        $this->createParamBuilder('test')->setType('void');
176    }
177
178    public function testInvalidTypeError(): void {
179        $this->expectException(\LogicException::class);
180        $this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or ComplexType');
181        $this->createParamBuilder('test')->setType(new \stdClass());
182    }
183
184    public function testByRef(): void {
185        $node = $this->createParamBuilder('test')
186            ->makeByRef()
187            ->getNode()
188        ;
189
190        $this->assertEquals(
191            new Node\Param(new Expr\Variable('test'), null, null, true),
192            $node
193        );
194    }
195
196    public function testVariadic(): void {
197        $node = $this->createParamBuilder('test')
198            ->makeVariadic()
199            ->getNode()
200        ;
201
202        $this->assertEquals(
203            new Node\Param(new Expr\Variable('test'), null, null, false, true),
204            $node
205        );
206    }
207
208    public function testMakePublic(): void {
209        $node = $this->createParamBuilder('test')
210            ->makePublic()
211            ->getNode()
212        ;
213
214        $this->assertEquals(
215            new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PUBLIC),
216            $node
217        );
218    }
219
220    public function testMakeProtected(): void {
221        $node = $this->createParamBuilder('test')
222            ->makeProtected()
223            ->getNode()
224        ;
225
226        $this->assertEquals(
227            new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED),
228            $node
229        );
230
231        $node = $this->createParamBuilder('test')
232            ->makeProtectedSet()
233            ->getNode()
234        ;
235
236        $this->assertEquals(
237            new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED_SET),
238            $node
239        );
240    }
241
242    public function testMakePrivate(): void {
243        $node = $this->createParamBuilder('test')
244            ->makePrivate()
245            ->getNode()
246        ;
247
248        $this->assertEquals(
249            new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE),
250            $node
251        );
252
253        $node = $this->createParamBuilder('test')
254            ->makePrivateSet()
255            ->getNode()
256        ;
257
258        $this->assertEquals(
259            new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE_SET),
260            $node
261        );
262    }
263
264    public function testMakeReadonly(): void {
265        $node = $this->createParamBuilder('test')
266            ->makeReadonly()
267            ->getNode()
268        ;
269
270        $this->assertEquals(
271            new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::READONLY),
272            $node
273        );
274    }
275
276    public function testAddAttribute(): void {
277        $attribute = new Attribute(
278            new Name('Attr'),
279            [new Arg(new Int_(1), false, false, [], new Identifier('name'))]
280        );
281        $attributeGroup = new AttributeGroup([$attribute]);
282
283        $node = $this->createParamBuilder('attributeGroup')
284            ->addAttribute($attributeGroup)
285            ->getNode();
286
287        $this->assertEquals(
288            new Node\Param(new Expr\Variable('attributeGroup'), null, null, false, false, [], 0, [$attributeGroup]),
289            $node
290        );
291    }
292}
293