Skip to content

Commit 6cde304

Browse files
committed
Add support for interface methods
1 parent 5781433 commit 6cde304

File tree

4 files changed

+408
-4
lines changed

4 files changed

+408
-4
lines changed

src/Code/MethodGenerator.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class MethodGenerator extends AbstractMemberGenerator
2929
private $parameters = [];
3030

3131
/**
32-
* @var BodyGenerator
32+
* @var BodyGenerator|null
3333
*/
3434
private $body;
3535

@@ -42,10 +42,10 @@ final class MethodGenerator extends AbstractMemberGenerator
4242
* @param string $name
4343
* @param array $parameters
4444
* @param int $flags
45-
* @param BodyGenerator $body
45+
* @param BodyGenerator|null $body
4646
*/
4747
public function __construct(
48-
$name,
48+
string $name,
4949
array $parameters = [],
5050
$flags = self::FLAG_PUBLIC,
5151
BodyGenerator $body = null
@@ -154,9 +154,17 @@ static function (ParameterGenerator $parameter) {
154154
},
155155
$this->getParameters()
156156
),
157-
'stmts' => $this->body ? $this->body->generate() : [],
157+
'stmts' => $this->body ? $this->body->generate() : null,
158158
'returnType' => $this->returnType ? $this->returnType->generate() : null,
159159
]
160160
);
161161
}
162+
163+
public function withoutBody(): self
164+
{
165+
$self = clone $this;
166+
$self->body = null;
167+
168+
return $self;
169+
}
162170
}

src/NodeVisitor/InterfaceMethod.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/**
4+
* @see https://github.com/open-code-modeling/php-code-ast for the canonical source repository
5+
* @copyright https://github.com/open-code-modeling/php-code-ast/blob/master/COPYRIGHT.md
6+
* @license https://github.com/open-code-modeling/php-code-ast/blob/master/LICENSE.md MIT License
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace OpenCodeModeling\CodeAst\NodeVisitor;
12+
13+
use OpenCodeModeling\CodeAst\Code\MethodGenerator;
14+
use PhpParser\Node;
15+
use PhpParser\Node\Stmt;
16+
use PhpParser\Node\Stmt\Interface_;
17+
use PhpParser\Node\Stmt\Namespace_;
18+
use PhpParser\NodeVisitorAbstract;
19+
20+
final class InterfaceMethod extends NodeVisitorAbstract
21+
{
22+
/**
23+
* @var MethodGenerator
24+
**/
25+
private $methodGenerator;
26+
27+
public function __construct(MethodGenerator $methodGenerator)
28+
{
29+
$this->methodGenerator = $methodGenerator->withoutBody();
30+
}
31+
32+
public function afterTraverse(array $nodes): ?array
33+
{
34+
$newNodes = [];
35+
36+
foreach ($nodes as $node) {
37+
$newNodes[] = $node;
38+
39+
if ($node instanceof Namespace_) {
40+
foreach ($node->stmts as $stmt) {
41+
if ($stmt instanceof Stmt\Interface_) {
42+
if ($this->checkMethodExists($stmt)) {
43+
return null;
44+
}
45+
$stmt->stmts[] = $this->methodGenerator->generate();
46+
}
47+
}
48+
} elseif ($node instanceof Stmt\Interface_) {
49+
if ($this->checkMethodExists($node)) {
50+
return null;
51+
}
52+
$node->stmts[] = $this->methodGenerator->generate();
53+
}
54+
}
55+
56+
return $newNodes;
57+
}
58+
59+
private function checkMethodExists(Interface_ $node): bool
60+
{
61+
foreach ($node->stmts as $stmt) {
62+
if ($stmt instanceof Node\Stmt\ClassMethod
63+
&& $stmt->name->name === $this->methodGenerator->getName()
64+
) {
65+
return true;
66+
}
67+
}
68+
69+
return false;
70+
}
71+
}

tests/NodeVisitor/ClassMethodTest.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenCodeModelingTest\CodeAst\NodeVisitor;
6+
7+
use OpenCodeModeling\CodeAst\Code\BodyGenerator;
8+
use OpenCodeModeling\CodeAst\Code\ClassGenerator;
9+
use OpenCodeModeling\CodeAst\Code\MethodGenerator;
10+
use OpenCodeModeling\CodeAst\Code\ParameterGenerator;
11+
use OpenCodeModeling\CodeAst\NodeVisitor\ClassFile;
12+
use OpenCodeModeling\CodeAst\NodeVisitor\ClassMethod;
13+
use OpenCodeModeling\CodeAst\NodeVisitor\ClassNamespace;
14+
use PhpParser\NodeTraverser;
15+
use PhpParser\Parser;
16+
use PhpParser\ParserFactory;
17+
use PhpParser\PrettyPrinter\Standard;
18+
use PHPUnit\Framework\TestCase;
19+
20+
final class ClassMethodTest extends TestCase
21+
{
22+
/**
23+
* @var Parser
24+
*/
25+
private $parser;
26+
27+
/**
28+
* @var Standard
29+
*/
30+
private $printer;
31+
32+
/**
33+
* @var MethodGenerator
34+
*/
35+
private $method;
36+
37+
public function setUp(): void
38+
{
39+
$this->parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7);
40+
$this->printer = new Standard(['shortArraySyntax' => true]);
41+
$this->method = new MethodGenerator(
42+
'testMethod',
43+
[
44+
new ParameterGenerator('arg', 'string'),
45+
],
46+
MethodGenerator::FLAG_PUBLIC,
47+
new BodyGenerator($this->parser, "return 'test';") // will be removed by visitor
48+
);
49+
$this->method->setReturnType('string');
50+
}
51+
52+
/**
53+
* @test
54+
*/
55+
public function it_generates_method_for_class_for_empty_file(): void
56+
{
57+
$ast = $this->parser->parse('');
58+
59+
$nodeTraverser = new NodeTraverser();
60+
$nodeTraverser->addVisitor(new ClassFile(new ClassGenerator('TestClass')));
61+
$nodeTraverser->addVisitor(new ClassMethod($this->method));
62+
63+
$expected = <<<'EOF'
64+
<?php
65+
66+
class TestClass
67+
{
68+
public function testMethod(string $arg) : string
69+
{
70+
return 'test';
71+
}
72+
}
73+
EOF;
74+
75+
$this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast)));
76+
}
77+
78+
/**
79+
* @test
80+
*/
81+
public function it_generates_method_for_class_for_existing_file(): void
82+
{
83+
$ast = $this->parser->parse('<?php class TestClass {}');
84+
85+
$nodeTraverser = new NodeTraverser();
86+
$nodeTraverser->addVisitor(new ClassMethod($this->method));
87+
88+
$expected = <<<'EOF'
89+
<?php
90+
91+
class TestClass
92+
{
93+
public function testMethod(string $arg) : string
94+
{
95+
return 'test';
96+
}
97+
}
98+
EOF;
99+
100+
$this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast)));
101+
}
102+
103+
/**
104+
* @test
105+
*/
106+
public function it_generates_method_for_class_with_namespace_for_empty_file(): void
107+
{
108+
$ast = $this->parser->parse('');
109+
110+
$nodeTraverser = new NodeTraverser();
111+
$nodeTraverser->addVisitor(new ClassNamespace('My\\Awesome\\Service'));
112+
$nodeTraverser->addVisitor(new ClassFile(new ClassGenerator('TestClass')));
113+
$nodeTraverser->addVisitor(new ClassMethod($this->method));
114+
115+
$expected = <<<'EOF'
116+
<?php
117+
118+
namespace My\Awesome\Service;
119+
120+
class TestClass
121+
{
122+
public function testMethod(string $arg) : string
123+
{
124+
return 'test';
125+
}
126+
}
127+
EOF;
128+
129+
$this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast)));
130+
}
131+
132+
/**
133+
* @test
134+
*/
135+
public function it_generates_method_for_class_with_namespace_for_existing_file(): void
136+
{
137+
$code = <<<'EOF'
138+
<?php
139+
140+
namespace My\Awesome\Service;
141+
142+
class TestClass {}
143+
EOF;
144+
145+
$ast = $this->parser->parse($code);
146+
147+
$nodeTraverser = new NodeTraverser();
148+
$nodeTraverser->addVisitor(new ClassMethod($this->method));
149+
150+
$expected = <<<'EOF'
151+
<?php
152+
153+
namespace My\Awesome\Service;
154+
155+
class TestClass
156+
{
157+
public function testMethod(string $arg) : string
158+
{
159+
return 'test';
160+
}
161+
}
162+
EOF;
163+
164+
$this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast)));
165+
}
166+
167+
}

0 commit comments

Comments
 (0)