From 1734ee017f34e7a28ddcf2e3bfacca4a3306578e Mon Sep 17 00:00:00 2001 From: Nyholm Date: Fri, 23 Apr 2021 21:48:49 +0200 Subject: [PATCH] Speed up things by using nikic/php-parser --- composer.json | 1 + src/Service/CodeValidator/PhpValidator.php | 38 +++++++------- tests/Service/Validator/PhpValidatorTest.php | 54 ++++++++++++++++++-- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 9121f91..be13073 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "ext-iconv": "*", "ext-libxml": "*", "doctrine/rst-parser": "^0.4", + "nikic/php-parser": "^4.10", "symfony/console": "^5.2", "symfony/docs-builder": "^0.15.0", "symfony/dotenv": "^5.2", diff --git a/src/Service/CodeValidator/PhpValidator.php b/src/Service/CodeValidator/PhpValidator.php index f1197fc..3a7a0a1 100644 --- a/src/Service/CodeValidator/PhpValidator.php +++ b/src/Service/CodeValidator/PhpValidator.php @@ -3,12 +3,16 @@ namespace SymfonyTools\CodeBlockChecker\Service\CodeValidator; use Doctrine\RST\Nodes\CodeNode; -use Symfony\Component\Process\Process; +use PhpParser\ErrorHandler; +use PhpParser\Parser; +use PhpParser\ParserFactory; use SymfonyTools\CodeBlockChecker\Issue\Issue; use SymfonyTools\CodeBlockChecker\Issue\IssueCollection; class PhpValidator implements Validator { + private ?Parser $parser = null; + public function validate(CodeNode $node, IssueCollection $issues): void { $language = $node->getLanguage() ?? ($node->isRaw() ? null : 'php'); @@ -16,32 +20,27 @@ public function validate(CodeNode $node, IssueCollection $issues): void return; } - $file = sys_get_temp_dir().'/'.uniqid('doc_builder', true).'.php'; - - file_put_contents($file, $this->getContents($node, $language)); + $linesPrepended = 0; + $code = 'html+php' === $language ? $node->getValue() : $this->getContents($node, $linesPrepended); + $this->getParser()->parse($code, $errorHandler = new ErrorHandler\Collecting()); - $process = new Process(['php', '-l', $file]); - $process->run(); - if ($process->isSuccessful()) { - return; + foreach ($errorHandler->getErrors() as $error) { + $issues->addIssue(new Issue($node, $error->getRawMessage(), 'PHP syntax', $node->getEnvironment()->getCurrentFileName(), $error->getStartLine() - $linesPrepended)); } + } - $line = 0; - $text = str_replace($file, 'example.php', $process->getErrorOutput()); - if (preg_match('| in example.php on line ([0-9]+)|s', $text, $matches)) { - $text = str_replace($matches[0], '', $text); - $line = ((int) $matches[1]) - 1; // we added "parser) { + $this->parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); } - $issues->addIssue(new Issue($node, $text, 'PHP syntax', $node->getEnvironment()->getCurrentFileName(), $line)); + + return $this->parser; } - private function getContents(CodeNode $node, string $language): string + private function getContents(CodeNode $node, &$linesPrepended = null): string { $contents = $node->getValue(); - if ('html+php' === $language) { - return $contents; - } - if (!preg_match('#(class|interface) [a-zA-Z]+#s', $contents) && preg_match('#(public|protected|private)( static)? (\$[a-z]+|function)#s', $contents)) { $contents = 'class Foobar {'.$contents.'}'; } @@ -52,6 +51,7 @@ private function getContents(CodeNode $node, string $language): string $lines = explode("\n", $contents); if (!str_contains($lines[0] ?? '', 'validator = new PhpValidator(); } + public function testLocalLine() + { + // Without setEnvironment($this->environment); + $node->setLanguage('php'); + $this->validator->validate($node, $issues = new IssueCollection()); + $this->assertCount(1, $issues); + $this->assertEquals(4, $issues->first()->getLocalLine()); + + // With setEnvironment($this->environment); + $node->setLanguage('php'); + $this->validator->validate($node, $issues = new IssueCollection()); + $this->assertCount(1, $issues); + $this->assertEquals(5, $issues->first()->getLocalLine()); + } + /** * @dataProvider getCodeExamples */ - public function testCodeExamples(int $errors, string $code) + public function testCodeExamples(int $errors, string $code, ?string $language = null) { $node = new CodeNode(explode(PHP_EOL, $code)); $node->setEnvironment($this->environment); - $node->setLanguage('php'); - $issues = new IssueCollection(); - $this->validator->validate($node, $issues); + $node->setLanguage($language ?? 'php'); + $this->validator->validate($node, $issues = new IssueCollection()); $this->assertCount($errors, $issues); } @@ -70,5 +101,20 @@ public static function validate($object, ExecutionContextInterface $context, $pa } } ']; + yield [1, ' +public static function validate($object, ExecutionContextInterface $context, $payload) +{ + $foo = 2a +} +']; + yield [0, ' +

Hello

+

+', 'html+php']; + + yield [1, ' +

Hello

+

+', 'html+php']; } }