Skip to content

Commit 7900a3d

Browse files
committed
Provide node visitor and generator for Interface - Close #10
1 parent cbeb31f commit 7900a3d

File tree

4 files changed

+334
-0
lines changed

4 files changed

+334
-0
lines changed

src/Code/InterfaceGenerator.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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\Code;
12+
13+
use PhpParser\Builder;
14+
use PhpParser\Node\Stmt\Interface_;
15+
16+
/**
17+
* Code is largely lifted from the Zend\Code\Generator\ClassGenerator implementation in
18+
* Zend Code, released with the copyright and license below. It is modified to work with PHP AST.
19+
*
20+
* @see https://github.com/zendframework/zend-code for the canonical source repository
21+
* @copyright Copyright (c) 2005-2019 Zend Technologies USA Inc. (http://www.zend.com)
22+
* @license https://github.com/zendframework/zend-code/blob/master/LICENSE.md New BSD License
23+
*/
24+
final class InterfaceGenerator implements StatementGenerator
25+
{
26+
/**
27+
* @var string
28+
*/
29+
private $name;
30+
31+
/**
32+
* @param string $name
33+
*/
34+
public function __construct(string $name)
35+
{
36+
$this->setName($name);
37+
}
38+
39+
public function generate(): Interface_
40+
{
41+
$classBuilder = new Builder\Interface_($this->name);
42+
43+
return $classBuilder->getNode();
44+
}
45+
46+
/**
47+
* @param string $name
48+
* @return self
49+
*/
50+
public function setName(string $name): self
51+
{
52+
$this->name = $name;
53+
54+
return $this;
55+
}
56+
57+
/**
58+
* @return string
59+
*/
60+
public function getName(): string
61+
{
62+
return $this->name;
63+
}
64+
}

src/NodeVisitor/InterfaceFile.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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\InterfaceGenerator;
14+
use PhpParser\Node\Stmt\Interface_;
15+
use PhpParser\Node\Stmt\Namespace_;
16+
use PhpParser\NodeVisitorAbstract;
17+
18+
final class InterfaceFile extends NodeVisitorAbstract
19+
{
20+
/**
21+
* @var bool
22+
*/
23+
private $classExists = false;
24+
25+
/**
26+
* @var InterfaceGenerator
27+
**/
28+
private $interfaceGenerator;
29+
30+
public function __construct(InterfaceGenerator $interfaceGenerator)
31+
{
32+
$this->interfaceGenerator = $interfaceGenerator;
33+
}
34+
35+
public function beforeTraverse(array $nodes)
36+
{
37+
foreach ($nodes as $node) {
38+
if ($node instanceof Namespace_) {
39+
foreach ($node->stmts as $stmt) {
40+
if ($stmt instanceof Interface_) {
41+
$this->classExists = $stmt->name->name === $this->interfaceGenerator->getName();
42+
}
43+
}
44+
} elseif ($node instanceof Interface_) {
45+
$this->classExists = $node->name->name === $this->interfaceGenerator->getName();
46+
}
47+
}
48+
49+
return null;
50+
}
51+
52+
public function afterTraverse(array $nodes): ?array
53+
{
54+
if ($this->classExists === false) {
55+
$nodes[] = $this->interfaceGenerator->generate();
56+
57+
return $nodes;
58+
}
59+
60+
return null;
61+
}
62+
}

tests/Code/InterfaceGeneratorTest.php

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

0 commit comments

Comments
 (0)