Skip to content

Commit 233af7b

Browse files
committed
Refactor everything
1 parent d96a4a4 commit 233af7b

File tree

7 files changed

+194
-91
lines changed

7 files changed

+194
-91
lines changed

config/services.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ services:
1010

1111
_instanceof:
1212
Symfony\CodeBlockChecker\Service\CodeValidator\Validator:
13-
tags:
14-
- 'app.code_validator'
13+
tags: ['app.code_validator']
14+
Symfony\CodeBlockChecker\Service\CodeRunner\Runner:
15+
tags: ['app.code_runner']
1516

1617
Symfony\CodeBlockChecker\:
1718
resource: '../src/'
@@ -21,3 +22,6 @@ services:
2122

2223
Symfony\CodeBlockChecker\Service\CodeValidator:
2324
arguments: [!tagged_iterator app.code_validator]
25+
26+
Symfony\CodeBlockChecker\Service\CodeRunner:
27+
arguments: [!tagged_iterator app.code_runner]

src/Command/CheckDocsCommand.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\CodeBlockChecker\Listener\CodeNodeCollector;
1515
use Symfony\CodeBlockChecker\Service\Baseline;
1616
use Symfony\CodeBlockChecker\Service\CodeNodeRunner;
17+
use Symfony\CodeBlockChecker\Service\CodeRunner;
1718
use Symfony\CodeBlockChecker\Service\CodeValidator;
1819
use Symfony\Component\Console\Command\Command;
1920
use Symfony\Component\Console\Input\InputArgument;
@@ -34,14 +35,14 @@ class CheckDocsCommand extends Command
3435
private CodeNodeCollector $collector;
3536
private CodeValidator $validator;
3637
private Baseline $baseline;
37-
private CodeNodeRunner $codeNodeRunner;
38+
private CodeRunner $codeRunner;
3839

39-
public function __construct(CodeValidator $validator, Baseline $baseline, CodeNodeRunner $codeNodeRunner)
40+
public function __construct(CodeValidator $validator, Baseline $baseline, CodeRunner $codeRunner)
4041
{
4142
parent::__construct(self::$defaultName);
4243
$this->validator = $validator;
4344
$this->baseline = $baseline;
44-
$this->codeNodeRunner = $codeNodeRunner;
45+
$this->codeRunner = $codeRunner;
4546
}
4647

4748
protected function configure()
@@ -88,7 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8889
// Verify code blocks
8990
$issues = $this->validator->validateNodes($this->collector->getNodes());
9091
if ($applicationDir = $input->getOption('symfony-application')) {
91-
$issues->append($this->codeNodeRunner->runNodes($this->collector->getNodes(), $applicationDir));
92+
$issues->append($this->codeRunner->runNodes($this->collector->getNodes(), $applicationDir));
9293
}
9394

9495
if ($baselineFile = $input->getOption('generate-baseline')) {

src/Service/CodeRunner.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\CodeBlockChecker\Service;
6+
7+
use Doctrine\RST\Nodes\CodeNode;
8+
use Symfony\CodeBlockChecker\Issue\IssueCollection;
9+
use Symfony\CodeBlockChecker\Service\CodeRunner\Runner;
10+
use Symfony\CodeBlockChecker\Service\CodeValidator\Validator;
11+
12+
/**
13+
* Run a Code Node inside a real application
14+
*
15+
* @author Tobias Nyholm <[email protected]>
16+
*/
17+
class CodeRunner
18+
{
19+
/**
20+
* @var iterable<Runner>
21+
*/
22+
private $runners;
23+
24+
/**
25+
* @param iterable<Runner> $runners
26+
*/
27+
public function __construct(iterable $runners)
28+
{
29+
$this->runners = $runners;
30+
}
31+
32+
/**
33+
* @param list<CodeNode> $nodes
34+
*/
35+
public function runNodes(array $nodes, string $applicationDirectory): IssueCollection
36+
{
37+
$issues = new IssueCollection();
38+
foreach ($this->runners as $runner) {
39+
$runner->run($nodes, $issues, $applicationDirectory);
40+
}
41+
42+
return $issues;
43+
}
44+
}

src/Service/CodeRunner/ClassExist.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace Symfony\CodeBlockChecker\Service\CodeRunner;
4+
5+
use Doctrine\RST\Nodes\CodeNode;
6+
use Symfony\CodeBlockChecker\Issue\Issue;
7+
use Symfony\CodeBlockChecker\Issue\IssueCollection;
8+
use Symfony\CodeBlockChecker\Service\CodeRunner\Runner;
9+
use Symfony\Component\Filesystem\Filesystem;
10+
use Symfony\Component\Process\Process;
11+
12+
/**
13+
* Verify that any reference to a PHP class is actually a real class.
14+
*
15+
* @author Tobias Nyholm <[email protected]>
16+
*/
17+
class ClassExist implements Runner
18+
{
19+
/**
20+
* @param list<CodeNode> $nodes
21+
*/
22+
public function run(array $nodes, IssueCollection $issues, string $applicationDirectory): void
23+
{
24+
$classes = [];
25+
foreach ($nodes as $node) {
26+
$classes = array_merge($classes, $this->getClasses($node));
27+
}
28+
29+
$this->testClasses($classes, $issues, $applicationDirectory);
30+
}
31+
32+
/**
33+
* @param list<string> $codeBlock
34+
*/
35+
private function getClasses(CodeNode $node): array
36+
{
37+
$codeBlock = explode("\n", $node->getValue());
38+
$language = $node->getLanguage() ?? 'php';
39+
if (!in_array($language, ['php', 'php-symfony', 'php-standalone', 'php-annotations'])) {
40+
return [];
41+
}
42+
43+
$classes = [];
44+
foreach ($codeBlock as $i => $line) {
45+
$matches = [];
46+
if (0 !== strpos($line, 'use ') || !preg_match('|^use (.*\\\.*); *?$|m', $line, $matches)) {
47+
continue;
48+
}
49+
50+
$class = $matches[1];
51+
if (false !== $pos = strpos($class, ' as ')) {
52+
$class = substr($class, 0, $pos);
53+
}
54+
55+
if (false !== $pos = strpos($class, 'function ')) {
56+
continue;
57+
}
58+
59+
$explode = explode('\\', $class);
60+
if (
61+
'App' === $explode[0] || 'Acme' === $explode[0]
62+
|| (3 === count($explode) && 'Symfony' === $explode[0] && ('Component' === $explode[1] || 'Config' === $explode[1]))
63+
) {
64+
continue;
65+
}
66+
67+
$classes[] = ['class' => $class, 'line' => $i + 1, 'node' => $node];
68+
}
69+
70+
return $classes;
71+
}
72+
73+
/**
74+
* Make sure PHP classes exists in the application directory.
75+
*
76+
* @param array{int, array{ class: string, line: int, node: CodeNode } } $classes
77+
*/
78+
private function testClasses(array $classes, IssueCollection $issues,string $applicationDirectory): void
79+
{
80+
$fileBody = '';
81+
foreach ($classes as $i => $data) {
82+
$fileBody .= sprintf('%s => isLoaded("%s"),', $i, $data['class']) . "\n";
83+
}
84+
85+
file_put_contents($applicationDirectory.'/class_exist.php', strtr('<?php
86+
require __DIR__.\'/vendor/autoload.php\';
87+
88+
function isLoaded($class) {
89+
return class_exists($class) || interface_exists($class) || trait_exists($class);
90+
}
91+
92+
echo json_encode([ARRAY_CONTENT]);
93+
94+
', ['ARRAY_CONTENT' => $fileBody]));
95+
96+
$process = new Process(['php', 'class_exist.php'], $applicationDirectory);
97+
$process->run();
98+
99+
if (!$process->isSuccessful()) {
100+
// TODO handle this
101+
return;
102+
}
103+
104+
$output = $process->getOutput();
105+
try {
106+
$results = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
107+
} catch (\JsonException $e) {
108+
// TODO handle this
109+
return;
110+
}
111+
112+
foreach ($classes as $i => $data) {
113+
if (!$results[$i]) {
114+
$text = sprintf('Class, interface or trait with name "%s" does not exist', $data['class']);
115+
$issues->addIssue(new Issue($data['node'], $text, 'Missing class', $data['node']->getEnvironment()->getCurrentFileName(), $data['line']));
116+
}
117+
}
118+
}
119+
}
Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Symfony\CodeBlockChecker\Service;
3+
namespace Symfony\CodeBlockChecker\Service\CodeRunner;
44

55
use Doctrine\RST\Nodes\CodeNode;
66
use Symfony\CodeBlockChecker\Issue\Issue;
@@ -18,21 +18,17 @@ class CodeNodeRunner
1818
/**
1919
* @param list<CodeNode> $nodes
2020
*/
21-
public function runNodes(array $nodes, string $applicationDirectory): IssueCollection
21+
public function run(array $nodes, IssueCollection $issues, string $applicationDirectory): void
2222
{
23-
$issues = new IssueCollection();
2423
foreach ($nodes as $node) {
2524
$this->processNode($node, $issues, $applicationDirectory);
2625
}
27-
28-
return $issues;
2926
}
3027

3128
private function processNode(CodeNode $node, IssueCollection $issues, string $applicationDirectory): void
3229
{
3330
$explodedNode = explode("\n", $node->getValue());
3431
$file = $this->getFile($node, $explodedNode);
35-
$this->verifyPhpClasses($node, $issues, $explodedNode, $applicationDirectory);
3632

3733
if ('config/packages/' !== substr($file, 0, 16)) {
3834
return;
@@ -105,83 +101,4 @@ private function getNodeContents(CodeNode $node, array $contents): string
105101

106102
return implode("\n", $contents);
107103
}
108-
109-
/**
110-
* Make sure PHP classes exists in the application directory.
111-
*
112-
* @param list<string> $codeBlock
113-
*/
114-
private function verifyPhpClasses(CodeNode $node, IssueCollection $issues, array $codeBlock, string $applicationDirectory): void
115-
{
116-
$language = $node->getLanguage() ?? 'php';
117-
if (!in_array($language, ['php', 'php-symfony', 'php-standalone', 'php-annotations'])) {
118-
return;
119-
}
120-
121-
$fileBody = '';
122-
$classes = [];
123-
foreach ($codeBlock as $i => $line) {
124-
$matches = [];
125-
if (0 !== strpos($line, 'use ') || !preg_match('|^use (.*\\\.*); *?$|m', $line, $matches)) {
126-
continue;
127-
}
128-
129-
$class = $matches[1];
130-
if (false !== $pos = strpos($class, ' as ')) {
131-
$class = substr($class, 0, $pos);
132-
}
133-
134-
if (false !== $pos = strpos($class, 'function ')) {
135-
continue;
136-
}
137-
138-
$explode = explode('\\', $class);
139-
if (
140-
'App' === $explode[0] || 'Acme' === $explode[0]
141-
|| (3 === count($explode) && 'Symfony' === $explode[0] && ('Component' === $explode[1] || 'Config' === $explode[1]))
142-
) {
143-
continue;
144-
}
145-
146-
$classes[$i] = ['class' => $class, 'line' => $i + 1];
147-
$fileBody .= sprintf('%s => isLoaded("%s"),', $i, $class)."\n";
148-
}
149-
150-
if ([] === $classes) {
151-
return;
152-
}
153-
154-
file_put_contents($applicationDirectory.'/class_exist.php', strtr('<?php
155-
require __DIR__.\'/vendor/autoload.php\';
156-
157-
function isLoaded($class) {
158-
return class_exists($class) || interface_exists($class) || trait_exists($class);
159-
}
160-
161-
echo json_encode([ARRAY_CONTENT]);
162-
163-
', ['ARRAY_CONTENT' => $fileBody]));
164-
165-
$process = new Process(['php', 'class_exist.php'], $applicationDirectory);
166-
$process->run();
167-
168-
if (!$process->isSuccessful()) {
169-
// TODO handle this
170-
return;
171-
}
172-
173-
$output = $process->getOutput();
174-
try {
175-
$results = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
176-
} catch (\JsonException $e) {
177-
// TODO handle this
178-
return;
179-
}
180-
181-
foreach ($classes as $i => $classData) {
182-
if (!$results[$i]) {
183-
$issues->addIssue(new Issue($node, sprintf('Class, interface or trait with name "%s" does not exist', $classData['class']), 'Missing class', $node->getEnvironment()->getCurrentFileName(), $classData['line']));
184-
}
185-
}
186-
}
187104
}

src/Service/CodeRunner/Runner.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Symfony\CodeBlockChecker\Service\CodeRunner;
4+
5+
use Doctrine\RST\Nodes\CodeNode;
6+
use Symfony\CodeBlockChecker\Issue\IssueCollection;
7+
8+
interface Runner
9+
{
10+
/**
11+
* @param list<CodeNode> $nodes
12+
*/
13+
public function run(array $nodes, IssueCollection $issues, string $applicationDirectory): void;
14+
}

src/Service/CodeValidator.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Symfony\CodeBlockChecker\Service;
66

7+
use Doctrine\RST\Nodes\CodeNode;
78
use Symfony\CodeBlockChecker\Issue\IssueCollection;
89
use Symfony\CodeBlockChecker\Service\CodeValidator\Validator;
910

@@ -27,6 +28,9 @@ public function __construct(iterable $validators)
2728
$this->validators = $validators;
2829
}
2930

31+
/**
32+
* @param list<CodeNode> $nodes
33+
*/
3034
public function validateNodes(array $nodes): IssueCollection
3135
{
3236
$issues = new IssueCollection();

0 commit comments

Comments
 (0)