Skip to content

Commit 61e3635

Browse files
authored
Speed up things by using nikic/php-parser (#40)
1 parent 1be6d99 commit 61e3635

File tree

3 files changed

+70
-23
lines changed

3 files changed

+70
-23
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"ext-iconv": "*",
99
"ext-libxml": "*",
1010
"doctrine/rst-parser": "^0.4",
11+
"nikic/php-parser": "^4.10",
1112
"symfony/console": "^5.2",
1213
"symfony/docs-builder": "^0.15.0",
1314
"symfony/dotenv": "^5.2",

src/Service/CodeValidator/PhpValidator.php

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,44 @@
33
namespace SymfonyTools\CodeBlockChecker\Service\CodeValidator;
44

55
use Doctrine\RST\Nodes\CodeNode;
6-
use Symfony\Component\Process\Process;
6+
use PhpParser\ErrorHandler;
7+
use PhpParser\Parser;
8+
use PhpParser\ParserFactory;
79
use SymfonyTools\CodeBlockChecker\Issue\Issue;
810
use SymfonyTools\CodeBlockChecker\Issue\IssueCollection;
911

1012
class PhpValidator implements Validator
1113
{
14+
private ?Parser $parser = null;
15+
1216
public function validate(CodeNode $node, IssueCollection $issues): void
1317
{
1418
$language = $node->getLanguage() ?? ($node->isRaw() ? null : 'php');
1519
if (!in_array($language, ['php', 'php-symfony', 'php-standalone', 'php-annotations', 'html+php'])) {
1620
return;
1721
}
1822

19-
$file = sys_get_temp_dir().'/'.uniqid('doc_builder', true).'.php';
20-
21-
file_put_contents($file, $this->getContents($node, $language));
23+
$linesPrepended = 0;
24+
$code = 'html+php' === $language ? $node->getValue() : $this->getContents($node, $linesPrepended);
25+
$this->getParser()->parse($code, $errorHandler = new ErrorHandler\Collecting());
2226

23-
$process = new Process(['php', '-l', $file]);
24-
$process->run();
25-
if ($process->isSuccessful()) {
26-
return;
27+
foreach ($errorHandler->getErrors() as $error) {
28+
$issues->addIssue(new Issue($node, $error->getRawMessage(), 'PHP syntax', $node->getEnvironment()->getCurrentFileName(), $error->getStartLine() - $linesPrepended));
2729
}
30+
}
2831

29-
$line = 0;
30-
$text = str_replace($file, 'example.php', $process->getErrorOutput());
31-
if (preg_match('| in example.php on line ([0-9]+)|s', $text, $matches)) {
32-
$text = str_replace($matches[0], '', $text);
33-
$line = ((int) $matches[1]) - 1; // we added "<?php"
32+
private function getParser(): Parser
33+
{
34+
if (null === $this->parser) {
35+
$this->parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7);
3436
}
35-
$issues->addIssue(new Issue($node, $text, 'PHP syntax', $node->getEnvironment()->getCurrentFileName(), $line));
37+
38+
return $this->parser;
3639
}
3740

38-
private function getContents(CodeNode $node, string $language): string
41+
private function getContents(CodeNode $node, &$linesPrepended = null): string
3942
{
4043
$contents = $node->getValue();
41-
if ('html+php' === $language) {
42-
return $contents;
43-
}
44-
4544
if (!preg_match('#(class|interface) [a-zA-Z]+#s', $contents) && preg_match('#(public|protected|private)( static)? (\$[a-z]+|function)#s', $contents)) {
4645
$contents = 'class Foobar {'.$contents.'}';
4746
}
@@ -52,6 +51,7 @@ private function getContents(CodeNode $node, string $language): string
5251
$lines = explode("\n", $contents);
5352
if (!str_contains($lines[0] ?? '', '<?php') && !str_contains($lines[1] ?? '', '<?php') && !str_contains($lines[2] ?? '', '<?php')) {
5453
$contents = '<?php'."\n".$contents;
54+
$linesPrepended = 1;
5555
}
5656

5757
return $contents;

tests/Service/Validator/PhpValidatorTest.php

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,47 @@ protected function setUp(): void
2121
$this->validator = new PhpValidator();
2222
}
2323

24+
public function testLocalLine()
25+
{
26+
// Without <?php
27+
$code = '$x = 2;
28+
$y = 3;
29+
$z = 4
30+
echo "foo";
31+
';
32+
33+
$node = new CodeNode(explode(PHP_EOL, $code));
34+
$node->setEnvironment($this->environment);
35+
$node->setLanguage('php');
36+
$this->validator->validate($node, $issues = new IssueCollection());
37+
$this->assertCount(1, $issues);
38+
$this->assertEquals(4, $issues->first()->getLocalLine());
39+
40+
// With <?php
41+
$code = '<?php
42+
$x = 2;
43+
$y = 3;
44+
$z = 4
45+
echo "foo";
46+
';
47+
48+
$node = new CodeNode(explode(PHP_EOL, $code));
49+
$node->setEnvironment($this->environment);
50+
$node->setLanguage('php');
51+
$this->validator->validate($node, $issues = new IssueCollection());
52+
$this->assertCount(1, $issues);
53+
$this->assertEquals(5, $issues->first()->getLocalLine());
54+
}
55+
2456
/**
2557
* @dataProvider getCodeExamples
2658
*/
27-
public function testCodeExamples(int $errors, string $code)
59+
public function testCodeExamples(int $errors, string $code, ?string $language = null)
2860
{
2961
$node = new CodeNode(explode(PHP_EOL, $code));
3062
$node->setEnvironment($this->environment);
31-
$node->setLanguage('php');
32-
$issues = new IssueCollection();
33-
$this->validator->validate($node, $issues);
63+
$node->setLanguage($language ?? 'php');
64+
$this->validator->validate($node, $issues = new IssueCollection());
3465
$this->assertCount($errors, $issues);
3566
}
3667

@@ -70,5 +101,20 @@ public static function validate($object, ExecutionContextInterface $context, $pa
70101
}
71102
}
72103
'];
104+
yield [1, '
105+
public static function validate($object, ExecutionContextInterface $context, $payload)
106+
{
107+
$foo = 2a
108+
}
109+
'];
110+
yield [0, '
111+
<h1>Hello</h1>
112+
<p><? echo $value; ?></p>
113+
', 'html+php'];
114+
115+
yield [1, '
116+
<h1>Hello</h1>
117+
<p><?php value foobar ?></p>
118+
', 'html+php'];
73119
}
74120
}

0 commit comments

Comments
 (0)