1<?php declare(strict_types=1);
2
3namespace PhpParser\Builder;
4
5use PhpParser\Comment;
6use PhpParser\Modifiers;
7use PhpParser\Node;
8use PhpParser\Node\Arg;
9use PhpParser\Node\Attribute;
10use PhpParser\Node\AttributeGroup;
11use PhpParser\Node\Identifier;
12use PhpParser\Node\Name;
13use PhpParser\Node\Scalar\Int_;
14use PhpParser\Node\Stmt;
15
16class ClassTest extends \PHPUnit\Framework\TestCase {
17    protected function createClassBuilder($class) {
18        return new Class_($class);
19    }
20
21    public function testExtendsImplements(): void {
22        $node = $this->createClassBuilder('SomeLogger')
23            ->extend('BaseLogger')
24            ->implement('Namespaced\Logger', new Name('SomeInterface'))
25            ->implement('\Fully\Qualified', 'namespace\NamespaceRelative')
26            ->getNode()
27        ;
28
29        $this->assertEquals(
30            new Stmt\Class_('SomeLogger', [
31                'extends' => new Name('BaseLogger'),
32                'implements' => [
33                    new Name('Namespaced\Logger'),
34                    new Name('SomeInterface'),
35                    new Name\FullyQualified('Fully\Qualified'),
36                    new Name\Relative('NamespaceRelative'),
37                ],
38            ]),
39            $node
40        );
41    }
42
43    public function testAbstract(): void {
44        $node = $this->createClassBuilder('Test')
45            ->makeAbstract()
46            ->getNode()
47        ;
48
49        $this->assertEquals(
50            new Stmt\Class_('Test', [
51                'flags' => Modifiers::ABSTRACT
52            ]),
53            $node
54        );
55    }
56
57    public function testFinal(): void {
58        $node = $this->createClassBuilder('Test')
59            ->makeFinal()
60            ->getNode()
61        ;
62
63        $this->assertEquals(
64            new Stmt\Class_('Test', [
65                'flags' => Modifiers::FINAL
66            ]),
67            $node
68        );
69    }
70
71    public function testReadonly(): void {
72        $node = $this->createClassBuilder('Test')
73            ->makeReadonly()
74            ->getNode()
75        ;
76
77        $this->assertEquals(
78            new Stmt\Class_('Test', [
79                'flags' => Modifiers::READONLY
80            ]),
81            $node
82        );
83    }
84
85    public function testStatementOrder(): void {
86        $method = new Stmt\ClassMethod('testMethod');
87        $property = new Stmt\Property(
88            Modifiers::PUBLIC,
89            [new Node\PropertyItem('testProperty')]
90        );
91        $const = new Stmt\ClassConst([
92            new Node\Const_('TEST_CONST', new Node\Scalar\String_('ABC'))
93        ]);
94        $use = new Stmt\TraitUse([new Name('SomeTrait')]);
95
96        $node = $this->createClassBuilder('Test')
97            ->addStmt($method)
98            ->addStmt($property)
99            ->addStmts([$const, $use])
100            ->getNode()
101        ;
102
103        $this->assertEquals(
104            new Stmt\Class_('Test', [
105                'stmts' => [$use, $const, $property, $method]
106            ]),
107            $node
108        );
109    }
110
111    public function testDocComment(): void {
112        $docComment = <<<'DOC'
113/**
114 * Test
115 */
116DOC;
117        $class = $this->createClassBuilder('Test')
118            ->setDocComment($docComment)
119            ->getNode();
120
121        $this->assertEquals(
122            new Stmt\Class_('Test', [], [
123                'comments' => [
124                    new Comment\Doc($docComment)
125                ]
126            ]),
127            $class
128        );
129
130        $class = $this->createClassBuilder('Test')
131            ->setDocComment(new Comment\Doc($docComment))
132            ->getNode();
133
134        $this->assertEquals(
135            new Stmt\Class_('Test', [], [
136                'comments' => [
137                    new Comment\Doc($docComment)
138                ]
139            ]),
140            $class
141        );
142    }
143
144    public function testAddAttribute(): void {
145        $attribute = new Attribute(
146            new Name('Attr'),
147            [new Arg(new Int_(1), false, false, [], new Identifier('name'))]
148        );
149        $attributeGroup = new AttributeGroup([$attribute]);
150
151        $class = $this->createClassBuilder('ATTR_GROUP')
152            ->addAttribute($attributeGroup)
153            ->getNode();
154
155        $this->assertEquals(
156            new Stmt\Class_('ATTR_GROUP', [
157                'attrGroups' => [
158                    $attributeGroup,
159                ]
160            ], []),
161            $class
162        );
163    }
164
165    public function testInvalidStmtError(): void {
166        $this->expectException(\LogicException::class);
167        $this->expectExceptionMessage('Unexpected node of type "Stmt_Echo"');
168        $this->createClassBuilder('Test')
169            ->addStmt(new Stmt\Echo_([]))
170        ;
171    }
172
173    public function testInvalidDocComment(): void {
174        $this->expectException(\LogicException::class);
175        $this->expectExceptionMessage('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
176        $this->createClassBuilder('Test')
177            ->setDocComment(new Comment('Test'));
178    }
179
180    public function testEmptyName(): void {
181        $this->expectException(\LogicException::class);
182        $this->expectExceptionMessage('Name cannot be empty');
183        $this->createClassBuilder('Test')
184            ->extend('');
185    }
186
187    public function testInvalidName(): void {
188        $this->expectException(\LogicException::class);
189        $this->expectExceptionMessage('Name must be a string or an instance of Node\Name');
190        $this->createClassBuilder('Test')
191            ->extend(['Foo']);
192    }
193}
194