diff --git a/README.md b/README.md index 15581b6..de3abe9 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,13 @@

-The PHP reference implementation for Type Language PhpDoc Parser. +Reference implementation for TypeLang PHPDoc Parser. Read [documentation pages](https://phpdoc.io) for more information. ## Installation -Type Language PHPDoc Parser is available as Composer repository and can +TypeLang PHPDoc Parser is available as Composer repository and can be installed using the following command in a root of your project: ```sh diff --git a/composer.json b/composer.json index b0fa397..660d67d 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "type-lang/phpdoc", "type": "library", - "description": "PHPDoc", + "description": "Library for recognizing PHPDoc annotations in PHP DocBlock comments", "keywords": ["language", "php", "phpdoc", "docblock", "tags", "dictionary"], "license": "MIT", "support": { diff --git a/src/Exception/ParsingException.php b/src/Exception/ParsingException.php index 8fec941..1ce47c6 100644 --- a/src/Exception/ParsingException.php +++ b/src/Exception/ParsingException.php @@ -10,20 +10,16 @@ class ParsingException extends \RuntimeException implements RuntimeExceptionInte protected const CODE_LAST = self::ERROR_CODE_INTERNAL; - public readonly string $source; - /** * @param int<0, max> $offset */ final public function __construct( - string $source, + public readonly string $source, public readonly int $offset = 0, string $message = "", int $code = 0, ?\Throwable $previous = null ) { - $this->source = $source; - parent::__construct($message, $code, $previous); } diff --git a/src/Factory.php b/src/Factory.php new file mode 100644 index 0000000..e4835d9 --- /dev/null +++ b/src/Factory.php @@ -0,0 +1,36 @@ + $factories + */ + public function __construct( + private array $factories = [], + ) {} + + public function add(array|string $tags, FactoryInterface $delegate): void + { + foreach ((array) $tags as $tag) { + $this->factories[$tag] = $delegate; + } + } + + public function create(string $name, string $content, DescriptionParserInterface $descriptions): Tag + { + $delegate = $this->factories[$name] ?? null; + + if ($delegate !== null) { + return $delegate->create($name, $content, $descriptions); + } + + return new Tag($name, $descriptions->parse($content)); + } +} diff --git a/src/FactoryInterface.php b/src/FactoryInterface.php new file mode 100644 index 0000000..41858bd --- /dev/null +++ b/src/FactoryInterface.php @@ -0,0 +1,18 @@ + $tags + */ + public function add(string|array $tags, FactoryInterface $delegate): void; +} diff --git a/src/Parser.php b/src/Parser.php index 48505d6..477b99f 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -21,11 +21,19 @@ */ class Parser implements ParserInterface { + private readonly CommentParserInterface $comments; + + private readonly DescriptionParserInterface $descriptions; + + private readonly TagParserInterface $tags; + public function __construct( - private readonly CommentParserInterface $comments = new RegexCommentParser(), - private readonly DescriptionParserInterface $descriptions = new SprintfDescriptionReader(), - private readonly TagParserInterface $tags = new TagParser(), - ) {} + FactoryInterface $tags = new Factory(), + ) { + $this->tags = new TagParser($tags); + $this->descriptions = new SprintfDescriptionReader($this->tags); + $this->comments = new RegexCommentParser(); + } /** * @throws RuntimeExceptionInterface @@ -75,7 +83,7 @@ private function analyze(string $docblock): \Generator foreach ($blocks->getReturn() as $block) { try { if ($description === null) { - $description = $this->descriptions->parse($block, $this->tags); + $description = $this->descriptions->parse($block); } else { $tags[] = $this->tags->parse($block, $this->descriptions); } diff --git a/src/Parser/Description/DescriptionParser.php b/src/Parser/Description/DescriptionParser.php index bd384f1..a282b32 100644 --- a/src/Parser/Description/DescriptionParser.php +++ b/src/Parser/Description/DescriptionParser.php @@ -9,16 +9,11 @@ abstract class DescriptionParser implements DescriptionParserInterface { - public function parse(string $description, TagParserInterface $parser = null): Description - { - if ($parser === null || $description === '') { - return new Description($description); - } - - return $this->doParseDescription($description, $parser); - } + public function __construct( + private readonly TagParserInterface $tags, + ) {} - private function doParseDescription(string $description, TagParserInterface $parser): Description + public function parse(string $description): Description { $tags = []; $tagIdentifier = 0; @@ -32,7 +27,7 @@ private function doParseDescription(string $description, TagParserInterface $par if (\str_starts_with($chunk, '@')) { try { - $tags[] = $parser->parse($chunk, $this); + $tags[] = $this->tags->parse($chunk, $this); $result .= $this->createDescriptionChunkPlaceholder(++$tagIdentifier); } catch (\Throwable) { $result .= "{{$chunk}}"; diff --git a/src/Parser/Description/DescriptionParserInterface.php b/src/Parser/Description/DescriptionParserInterface.php index 01a0596..596d745 100644 --- a/src/Parser/Description/DescriptionParserInterface.php +++ b/src/Parser/Description/DescriptionParserInterface.php @@ -4,7 +4,6 @@ namespace TypeLang\PHPDoc\Parser\Description; -use TypeLang\PHPDoc\Parser\Tag\TagParserInterface; use TypeLang\PHPDoc\Tag\Description; interface DescriptionParserInterface @@ -26,5 +25,5 @@ interface DescriptionParserInterface * // } * ``` */ - public function parse(string $description, TagParserInterface $parser = null): Description; + public function parse(string $description): Description; } diff --git a/src/Parser/Tag/TagParser.php b/src/Parser/Tag/TagParser.php index b49ffcd..512af85 100644 --- a/src/Parser/Tag/TagParser.php +++ b/src/Parser/Tag/TagParser.php @@ -5,6 +5,7 @@ namespace TypeLang\PHPDoc\Parser\Tag; use TypeLang\PHPDoc\Exception\InvalidTagNameException; +use TypeLang\PHPDoc\FactoryInterface; use TypeLang\PHPDoc\Parser\Description\DescriptionParserInterface; use TypeLang\PHPDoc\Tag\Tag; @@ -15,6 +16,10 @@ final class TagParser implements TagParserInterface */ private const PATTERN_TAG = '\G@[a-zA-Z_\x80-\xff\\\][\w\x80-\xff\-:\\\]*'; + public function __construct( + private readonly FactoryInterface $tags, + ) {} + /** * Read tag name from passed content. * @@ -54,33 +59,17 @@ private function getTagName(string $content): string } /** - * @return array{non-empty-string, string} * @throws InvalidTagNameException */ - private function getTagParts(string $content): array + public function parse(string $tag, DescriptionParserInterface $parser): Tag { - $name = $this->getTagName($content); + $name = $this->getTagName($tag); /** @var non-empty-string $name */ $name = \substr($name, 1); - $content = \substr($content, \strlen($name) + 1); + $content = \substr($tag, \strlen($name) + 1); $content = \ltrim($content); - return [$name, $content]; - } - - /** - * @throws InvalidTagNameException - */ - public function parse(string $tag, DescriptionParserInterface $parser = null): Tag - { - // Tag name like ["var", "example"] extracted from "@var example" - [$name, $content] = $this->getTagParts($tag); - - if ($parser !== null) { - $content = $parser->parse($content, $this); - } - - return new Tag($name, $content); + return $this->tags->create($name, $content, $parser); } } diff --git a/src/Parser/Tag/TagParserInterface.php b/src/Parser/Tag/TagParserInterface.php index d32fcaf..b54afb1 100644 --- a/src/Parser/Tag/TagParserInterface.php +++ b/src/Parser/Tag/TagParserInterface.php @@ -24,5 +24,5 @@ interface TagParserInterface * // } * ``` */ - public function parse(string $tag, DescriptionParserInterface $parser = null): Tag; + public function parse(string $tag, DescriptionParserInterface $parser): Tag; }