diff --git a/composer.json b/composer.json index 23067f3..10c604c 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "nikic/php-parser": "^5.0", "symfony/finder": "^5.4 || ^6.4 || ^7.0", "twig/twig": "^2.0 || ^3.0", - "doctrine/annotations": "^1.7 || ^2.0" + "phpstan/phpdoc-parser": "^2.3" }, "require-dev": { "symfony/phpunit-bridge": "^5.4 || ^6.4 || ^7.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f622df9..09455da 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,7 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - bootstrap="./tests/bootstrap.php" + bootstrap="./vendor/autoload.php" > diff --git a/src/Annotation/Desc.php b/src/Annotation/Desc.php index 20ebbad..32771ec 100644 --- a/src/Annotation/Desc.php +++ b/src/Annotation/Desc.php @@ -12,6 +12,8 @@ namespace Translation\Extractor\Annotation; /** + * @deprecated since 2.3, this class is not used anymore. @Desc is now considered as a PHPDoc tag. + * * @Annotation */ final class Desc diff --git a/src/Annotation/Ignore.php b/src/Annotation/Ignore.php index a95a0c6..bd33625 100644 --- a/src/Annotation/Ignore.php +++ b/src/Annotation/Ignore.php @@ -12,6 +12,8 @@ namespace Translation\Extractor\Annotation; /** + * @deprecated since 2.3, this class is not used anymore. @Ignore is now considered as a PHPDoc tag. + * * @Annotation */ final class Ignore diff --git a/src/Annotation/Translate.php b/src/Annotation/Translate.php index b8673a2..20e2852 100644 --- a/src/Annotation/Translate.php +++ b/src/Annotation/Translate.php @@ -12,6 +12,8 @@ namespace Translation\Extractor\Annotation; /** + * @deprecated since 2.3, this class is not used anymore. @Translate is now considered as a PHPDoc tag. + * * @Annotation */ class Translate diff --git a/src/Visitor/BaseVisitor.php b/src/Visitor/BaseVisitor.php index 565cc8e..8ad0102 100644 --- a/src/Visitor/BaseVisitor.php +++ b/src/Visitor/BaseVisitor.php @@ -11,11 +11,16 @@ namespace Translation\Extractor\Visitor; -use Doctrine\Common\Annotations\DocParser; use PhpParser\Node; +use PHPStan\PhpDocParser\Ast\ConstExpr\DoctrineConstExprStringNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; +use PHPStan\PhpDocParser\ParserConfig; use Symfony\Component\Finder\SplFileInfo; -use Translation\Extractor\Annotation\Desc; -use Translation\Extractor\Annotation\Ignore; use Translation\Extractor\Model\Error; use Translation\Extractor\Model\SourceCollection; use Translation\Extractor\Model\SourceLocation; @@ -27,7 +32,8 @@ */ abstract class BaseVisitor implements Visitor { - private ?DocParser $docParser = null; + protected ?Lexer $lexer = null; + protected ?PhpDocParser $phpDocParser = null; protected ?SourceCollection $collection = null; protected SplFileInfo $file; @@ -54,9 +60,11 @@ protected function addError(Node $node, string $errorMessage): void $line = $node->getAttribute('startLine'); } if (null !== $docComment) { - $context = 'file '.$file.' near line '.$line; - foreach ($this->getDocParser()->parse($docComment->getText(), $context) as $annotation) { - if ($annotation instanceof Ignore) { + $phpDocNode = $this->getPhpDocParser()->parse( + new TokenIterator($this->lexer->tokenize($docComment->getText())) + ); + foreach ($phpDocNode->getTags() as $tag) { + if ('@Ignore' === $tag->name) { return; } } @@ -78,12 +86,16 @@ protected function getLocation(string $text, int $line, ?Node $node = null, arra { $file = $this->getAbsoluteFilePath(); if (null !== $node && null !== $docComment = $node->getDocComment()) { - $parserContext = 'file '.$file.' near line '.$line; - foreach ($this->getDocParser()->parse($docComment->getText(), $parserContext) as $annotation) { - if ($annotation instanceof Ignore) { + $phpDocNode = $this->getPhpDocParser()->parse( + new TokenIterator($this->lexer->tokenize($docComment->getText())) + ); + foreach ($phpDocNode->getTags() as $tag) { + if ('@Ignore' === $tag->name) { return null; - } elseif ($annotation instanceof Desc) { - $context['desc'] = $annotation->text; + } elseif ('@Desc' === $tag->name && $tag->value instanceof DoctrineTagValueNode) { + if ([] !== $tag->value->annotation->arguments) { + $context['desc'] = DoctrineConstExprStringNode::unescape($tag->value->annotation->arguments[0]->value); + } } } } @@ -91,23 +103,21 @@ protected function getLocation(string $text, int $line, ?Node $node = null, arra return new SourceLocation($text, $file, $line, $context); } - private function getDocParser(): DocParser + protected function getPhpDocParser(): PhpDocParser { - if (null === $this->docParser) { - $this->docParser = new DocParser(); - - $this->docParser->setImports([ - 'ignore' => Ignore::class, - 'desc' => Desc::class, - ]); - $this->docParser->setIgnoreNotImportedAnnotations(true); + if (null === $this->phpDocParser) { + $config = new ParserConfig(usedAttributes: []); + $this->lexer = new Lexer($config); + $constExprParser = new ConstExprParser($config); + $typeParser = new TypeParser($config, $constExprParser); + $this->phpDocParser = new PhpDocParser($config, $typeParser, $constExprParser); } - return $this->docParser; + return $this->phpDocParser; } - public function setDocParser(DocParser $docParser): void + public function setPhpDocParser(PhpDocParser $phpDocParser): void { - $this->docParser = $docParser; + $this->phpDocParser = $phpDocParser; } } diff --git a/src/Visitor/Php/Symfony/FormTypeChoices.php b/src/Visitor/Php/Symfony/FormTypeChoices.php index 2e92f73..d407217 100644 --- a/src/Visitor/Php/Symfony/FormTypeChoices.php +++ b/src/Visitor/Php/Symfony/FormTypeChoices.php @@ -11,10 +11,9 @@ namespace Translation\Extractor\Visitor\Php\Symfony; -use Doctrine\Common\Annotations\DocParser; use PhpParser\Node; use PhpParser\NodeVisitor; -use Translation\Extractor\Annotation\Ignore; +use PHPStan\PhpDocParser\Parser\TokenIterator; use Translation\Extractor\Model\SourceLocation; /** @@ -134,20 +133,15 @@ public function enterNode(Node $node): ?Node protected function isIgnored(Node $node): bool { - // because of getDocParser method is private, we have to create a new custom instance - $docParser = new DocParser(); - $docParser->setImports([ - 'ignore' => Ignore::class, - ]); - $docParser->setIgnoreNotImportedAnnotations(true); - if (null !== $docComment = $node->getDocComment()) { - foreach ($docParser->parse($docComment->getText()) as $annotation) { - if ($annotation instanceof Ignore) { - return true; - } - } + if (null === $node->getDocComment()) { + return false; } - return false; + $phpDocNode = $this->getPhpDocParser()->parse( + new TokenIterator($this->lexer->tokenize($node->getDocComment()->getText())) + ); + $ignoreTags = $phpDocNode->getTagsByName('@Ignore'); + + return \count($ignoreTags) > 0; } } diff --git a/src/Visitor/Php/Symfony/ValidationAnnotation.php b/src/Visitor/Php/Symfony/ValidationAnnotation.php index 51c4617..3cd40a2 100644 --- a/src/Visitor/Php/Symfony/ValidationAnnotation.php +++ b/src/Visitor/Php/Symfony/ValidationAnnotation.php @@ -11,7 +11,6 @@ namespace Translation\Extractor\Visitor\Php\Symfony; -use Doctrine\Common\Annotations\AnnotationException; use PhpParser\Node; use PhpParser\NodeVisitor; use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -60,14 +59,8 @@ public function enterNode(Node $node): ?Node return null; } - try { - /** @var ClassMetadata $metadata */ - $metadata = $this->metadataFactory->getMetadataFor($name); - } catch (AnnotationException $e) { - $this->addError($node, \sprintf('Could not parse class "%s" for annotations. %s', $this->namespace, $e->getMessage())); - - return null; - } + /** @var ClassMetadata $metadata */ + $metadata = $this->metadataFactory->getMetadataFor($name); if (!$metadata->hasConstraints() && !\count($metadata->getConstrainedProperties())) { return null; diff --git a/src/Visitor/Php/TranslateAnnotationVisitor.php b/src/Visitor/Php/TranslateAnnotationVisitor.php index e9e2af3..22a0fc9 100644 --- a/src/Visitor/Php/TranslateAnnotationVisitor.php +++ b/src/Visitor/Php/TranslateAnnotationVisitor.php @@ -11,10 +11,12 @@ namespace Translation\Extractor\Visitor\Php; -use Doctrine\Common\Annotations\DocParser; use PhpParser\Comment; use PhpParser\Node; use PhpParser\NodeVisitor; +use PHPStan\PhpDocParser\Ast\ConstExpr\DoctrineConstExprStringNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode; +use PHPStan\PhpDocParser\Parser\TokenIterator; use Translation\Extractor\Annotation\Translate; /** @@ -24,22 +26,6 @@ */ class TranslateAnnotationVisitor extends BasePHPVisitor implements NodeVisitor { - protected ?DocParser $translateDocParser = null; - - private function getTranslateDocParser(): DocParser - { - if (null === $this->translateDocParser) { - $this->translateDocParser = new DocParser(); - - $this->translateDocParser->setImports([ - 'translate' => Translate::class, - ]); - $this->translateDocParser->setIgnoreNotImportedAnnotations(true); - } - - return $this->translateDocParser; - } - public function enterNode(Node $node): ?Node { // look for strings @@ -58,10 +44,21 @@ public function enterNode(Node $node): ?Node return null; } - foreach ($this->getTranslateDocParser()->parse($comment->getText()) as $annotation) { - // add phrase to dictionary - $this->addLocation($node->value, $node->getAttribute('startLine'), $node, ['domain' => $annotation->getDomain()]); - + $phpDocNode = $this->getPhpDocParser()->parse( + new TokenIterator($this->lexer->tokenize($comment->getText())) + ); + + $translateTags = $phpDocNode->getTagsByName('@Translate'); + if ([] !== $translateTags) { + $domain = 'messages'; + if ($translateTags[0]->value instanceof DoctrineTagValueNode) { + foreach ($translateTags[0]->value->annotation->arguments as $argument) { + if ('domain' === $argument->key->name) { + $domain = DoctrineConstExprStringNode::unescape($argument->value); + } + } + } + $this->addLocation($node->value, $node->getAttribute('startLine'), $node, ['domain' => $domain]); break; } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 92c0fac..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Doctrine\Common\Annotations\AnnotationRegistry; - -require __DIR__.'/../vendor/autoload.php'; - -if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { - if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { - AnnotationRegistry::registerUniqueLoader('class_exists'); - } elseif (method_exists(AnnotationRegistry::class, 'registerLoader')) { - AnnotationRegistry::registerLoader('class_exists'); - } -}