From 1c4f7ab3b838b23afb2ee4dab14acbf75956e952 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Tue, 22 Mar 2022 12:24:31 +0300 Subject: * add phpunit as a dev dependency * add some basic tests for UrlHelper::rewrite_relative() * fix UrlHelper::rewrite_relative() to work better on non-absolute relative URL paths --- .../src/DocBlock/Description.php | 115 +++++++ .../src/DocBlock/DescriptionFactory.php | 178 +++++++++++ .../src/DocBlock/ExampleFinder.php | 159 ++++++++++ .../src/DocBlock/Serializer.php | 157 ++++++++++ .../src/DocBlock/StandardTagFactory.php | 348 +++++++++++++++++++++ .../reflection-docblock/src/DocBlock/Tag.php | 31 ++ .../src/DocBlock/TagFactory.php | 84 +++++ .../src/DocBlock/Tags/Author.php | 102 ++++++ .../src/DocBlock/Tags/BaseTag.php | 53 ++++ .../src/DocBlock/Tags/Covers.php | 101 ++++++ .../src/DocBlock/Tags/Deprecated.php | 109 +++++++ .../src/DocBlock/Tags/Example.php | 200 ++++++++++++ .../src/DocBlock/Tags/Factory/StaticMethod.php | 25 ++ .../src/DocBlock/Tags/Formatter.php | 24 ++ .../src/DocBlock/Tags/Formatter/AlignFormatter.php | 50 +++ .../Tags/Formatter/PassthroughFormatter.php | 30 ++ .../src/DocBlock/Tags/Generic.php | 89 ++++++ .../src/DocBlock/Tags/InvalidTag.php | 145 +++++++++ .../reflection-docblock/src/DocBlock/Tags/Link.php | 78 +++++ .../src/DocBlock/Tags/Method.php | 279 +++++++++++++++++ .../src/DocBlock/Tags/Param.php | 174 +++++++++++ .../src/DocBlock/Tags/Property.php | 121 +++++++ .../src/DocBlock/Tags/PropertyRead.php | 121 +++++++ .../src/DocBlock/Tags/PropertyWrite.php | 121 +++++++ .../src/DocBlock/Tags/Reference/Fqsen.php | 38 +++ .../src/DocBlock/Tags/Reference/Reference.php | 22 ++ .../src/DocBlock/Tags/Reference/Url.php | 36 +++ .../src/DocBlock/Tags/Return_.php | 64 ++++ .../reflection-docblock/src/DocBlock/Tags/See.php | 106 +++++++ .../src/DocBlock/Tags/Since.php | 103 ++++++ .../src/DocBlock/Tags/Source.php | 116 +++++++ .../src/DocBlock/Tags/TagWithType.php | 66 ++++ .../src/DocBlock/Tags/Throws.php | 64 ++++ .../reflection-docblock/src/DocBlock/Tags/Uses.php | 100 ++++++ .../reflection-docblock/src/DocBlock/Tags/Var_.php | 122 ++++++++ .../src/DocBlock/Tags/Version.php | 106 +++++++ 36 files changed, 3837 insertions(+) create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php create mode 100644 vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php (limited to 'vendor/phpdocumentor/reflection-docblock/src/DocBlock') diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php new file mode 100644 index 000000000..a31b2892a --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.php @@ -0,0 +1,115 @@ +create('This is a {@see Description}', $context); + * + * The description factory will interpret the given body and create a body template and list of tags from them, and pass + * that onto the constructor if this class. + * + * > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace + * > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial + * > type names and FQSENs. + * + * If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this: + * + * $description = new Description( + * 'This is a %1$s', + * [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ] + * ); + * + * It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object + * is mainly responsible for rendering. + * + * @see DescriptionFactory to create a new Description. + * @see Description\Formatter for the formatting of the body and tags. + */ +class Description +{ + /** @var string */ + private $bodyTemplate; + + /** @var Tag[] */ + private $tags; + + /** + * Initializes a Description with its body (template) and a listing of the tags used in the body template. + * + * @param Tag[] $tags + */ + public function __construct(string $bodyTemplate, array $tags = []) + { + $this->bodyTemplate = $bodyTemplate; + $this->tags = $tags; + } + + /** + * Returns the body template. + */ + public function getBodyTemplate(): string + { + return $this->bodyTemplate; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags(): array + { + return $this->tags; + } + + /** + * Renders this description as a string where the provided formatter will format the tags in the expected string + * format. + */ + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new PassthroughFormatter(); + } + + $tags = []; + foreach ($this->tags as $tag) { + $tags[] = '{' . $formatter->format($tag) . '}'; + } + + return vsprintf($this->bodyTemplate, $tags); + } + + /** + * Returns a plain string representation of this description. + */ + public function __toString(): string + { + return $this->render(); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php new file mode 100644 index 000000000..1a519ec4a --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php @@ -0,0 +1,178 @@ +tagFactory = $tagFactory; + } + + /** + * Returns the parsed text of this description. + */ + public function create(string $contents, ?TypeContext $context = null): Description + { + $tokens = $this->lex($contents); + $count = count($tokens); + $tagCount = 0; + $tags = []; + + for ($i = 1; $i < $count; $i += 2) { + $tags[] = $this->tagFactory->create($tokens[$i], $context); + $tokens[$i] = '%' . ++$tagCount . '$s'; + } + + //In order to allow "literal" inline tags, the otherwise invalid + //sequence "{@}" is changed to "@", and "{}" is changed to "}". + //"%" is escaped to "%%" because of vsprintf. + //See unit tests for examples. + for ($i = 0; $i < $count; $i += 2) { + $tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]); + } + + return new Description(implode('', $tokens), $tags); + } + + /** + * Strips the contents from superfluous whitespace and splits the description into a series of tokens. + * + * @return string[] A series of tokens of which the description text is composed. + */ + private function lex(string $contents): array + { + $contents = $this->removeSuperfluousStartingWhitespace($contents); + + // performance optimalization; if there is no inline tag, don't bother splitting it up. + if (strpos($contents, '{@') === false) { + return [$contents]; + } + + return Utils::pregSplit( + '/\{ + # "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally. + (?!@\}) + # We want to capture the whole tag line, but without the inline tag delimiters. + (\@ + # Match everything up to the next delimiter. + [^{}]* + # Nested inline tag content should not be captured, or it will appear in the result separately. + (?: + # Match nested inline tags. + (?: + # Because we did not catch the tag delimiters earlier, we must be explicit with them here. + # Notice that this also matches "{}", as a way to later introduce it as an escape sequence. + \{(?1)?\} + | + # Make sure we match hanging "{". + \{ + ) + # Match content after the nested inline tag. + [^{}]* + )* # If there are more inline tags, match them as well. We use "*" since there may not be any + # nested inline tags. + ) + \}/Sux', + $contents, + 0, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * Removes the superfluous from a multi-line description. + * + * When a description has more than one line then it can happen that the second and subsequent lines have an + * additional indentation. This is commonly in use with tags like this: + * + * {@}since 1.1.0 This is an example + * description where we have an + * indentation in the second and + * subsequent lines. + * + * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent + * lines and this may cause rendering issues when, for example, using a Markdown converter. + */ + private function removeSuperfluousStartingWhitespace(string $contents): string + { + $lines = Utils::pregSplit("/\r\n?|\n/", $contents); + + // if there is only one line then we don't have lines with superfluous whitespace and + // can use the contents as-is + if (count($lines) <= 1) { + return $contents; + } + + // determine how many whitespace characters need to be stripped + $startingSpaceCount = 9999999; + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + // lines with a no length do not count as they are not indented at all + if (trim($lines[$i]) === '') { + continue; + } + + // determine the number of prefixing spaces by checking the difference in line length before and after + // an ltrim + $startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i]))); + } + + // strip the number of spaces from each line + if ($startingSpaceCount > 0) { + for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) { + $lines[$i] = substr($lines[$i], $startingSpaceCount); + } + } + + return implode("\n", $lines); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php new file mode 100644 index 000000000..6a6b47295 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.php @@ -0,0 +1,159 @@ +getFilePath(); + + $file = $this->getExampleFileContents($filename); + if (!$file) { + return sprintf('** File not found : %s **', $filename); + } + + return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount())); + } + + /** + * Registers the project's root directory where an 'examples' folder can be expected. + */ + public function setSourceDirectory(string $directory = ''): void + { + $this->sourceDirectory = $directory; + } + + /** + * Returns the project's root directory where an 'examples' folder can be expected. + */ + public function getSourceDirectory(): string + { + return $this->sourceDirectory; + } + + /** + * Registers a series of directories that may contain examples. + * + * @param string[] $directories + */ + public function setExampleDirectories(array $directories): void + { + $this->exampleDirectories = $directories; + } + + /** + * Returns a series of directories that may contain examples. + * + * @return string[] + */ + public function getExampleDirectories(): array + { + return $this->exampleDirectories; + } + + /** + * Attempts to find the requested example file and returns its contents or null if no file was found. + * + * This method will try several methods in search of the given example file, the first one it encounters is + * returned: + * + * 1. Iterates through all examples folders for the given filename + * 2. Checks the source folder for the given filename + * 3. Checks the 'examples' folder in the current working directory for examples + * 4. Checks the path relative to the current working directory for the given filename + * + * @return string[] all lines of the example file + */ + private function getExampleFileContents(string $filename): ?array + { + $normalizedPath = null; + + foreach ($this->exampleDirectories as $directory) { + $exampleFileFromConfig = $this->constructExamplePath($directory, $filename); + if (is_readable($exampleFileFromConfig)) { + $normalizedPath = $exampleFileFromConfig; + break; + } + } + + if (!$normalizedPath) { + if (is_readable($this->getExamplePathFromSource($filename))) { + $normalizedPath = $this->getExamplePathFromSource($filename); + } elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) { + $normalizedPath = $this->getExamplePathFromExampleDirectory($filename); + } elseif (is_readable($filename)) { + $normalizedPath = $filename; + } + } + + $lines = $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : false; + + return $lines !== false ? $lines : null; + } + + /** + * Get example filepath based on the example directory inside your project. + */ + private function getExamplePathFromExampleDirectory(string $file): string + { + return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file; + } + + /** + * Returns a path to the example file in the given directory.. + */ + private function constructExamplePath(string $directory, string $file): string + { + return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file; + } + + /** + * Get example filepath based on sourcecode. + */ + private function getExamplePathFromSource(string $file): string + { + return sprintf( + '%s%s%s', + trim($this->getSourceDirectory(), '\\/'), + DIRECTORY_SEPARATOR, + trim($file, '"') + ); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php new file mode 100644 index 000000000..77e5fb5fa --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.php @@ -0,0 +1,157 @@ +indent = $indent; + $this->indentString = $indentString; + $this->isFirstLineIndented = $indentFirstLine; + $this->lineLength = $lineLength; + $this->tagFormatter = $tagFormatter ?: new PassthroughFormatter(); + $this->lineEnding = $lineEnding; + } + + /** + * Generate a DocBlock comment. + * + * @param DocBlock $docblock The DocBlock to serialize. + * + * @return string The serialized doc block. + */ + public function getDocComment(DocBlock $docblock): string + { + $indent = str_repeat($this->indentString, $this->indent); + $firstIndent = $this->isFirstLineIndented ? $indent : ''; + // 3 === strlen(' * ') + $wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null; + + $text = $this->removeTrailingSpaces( + $indent, + $this->addAsterisksForEachLine( + $indent, + $this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength) + ) + ); + + $comment = $firstIndent . "/**\n"; + if ($text) { + $comment .= $indent . ' * ' . $text . "\n"; + $comment .= $indent . " *\n"; + } + + $comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment); + + return str_replace("\n", $this->lineEnding, $comment . $indent . ' */'); + } + + private function removeTrailingSpaces(string $indent, string $text): string + { + return str_replace( + sprintf("\n%s * \n", $indent), + sprintf("\n%s *\n", $indent), + $text + ); + } + + private function addAsterisksForEachLine(string $indent, string $text): string + { + return str_replace( + "\n", + sprintf("\n%s * ", $indent), + $text + ); + } + + private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, ?int $wrapLength): string + { + $text = $docblock->getSummary() . ((string) $docblock->getDescription() ? "\n\n" . $docblock->getDescription() + : ''); + if ($wrapLength !== null) { + $text = wordwrap($text, $wrapLength); + + return $text; + } + + return $text; + } + + private function addTagBlock(DocBlock $docblock, ?int $wrapLength, string $indent, string $comment): string + { + foreach ($docblock->getTags() as $tag) { + $tagText = $this->tagFormatter->format($tag); + if ($wrapLength !== null) { + $tagText = wordwrap($tagText, $wrapLength); + } + + $tagText = str_replace( + "\n", + sprintf("\n%s * ", $indent), + $tagText + ); + + $comment .= sprintf("%s * %s\n", $indent, $tagText); + } + + return $comment; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php new file mode 100644 index 000000000..8d7659510 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.php @@ -0,0 +1,348 @@ + Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise + * > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to + * > verify that a dependency is actually passed. + * + * This Factory also features a Service Locator component that is used to pass the right dependencies to the + * `create` method of a tag; each dependency should be registered as a service or as a parameter. + * + * When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass + * the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface. + */ +final class StandardTagFactory implements TagFactory +{ + /** PCRE regular expression matching a tag name. */ + public const REGEX_TAGNAME = '[\w\-\_\\\\:]+'; + + /** + * @var array> An array with a tag as a key, and an + * FQCN to a class that handles it as an array value. + */ + private $tagHandlerMappings = [ + 'author' => Author::class, + 'covers' => Covers::class, + 'deprecated' => Deprecated::class, + // 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example', + 'link' => LinkTag::class, + 'method' => Method::class, + 'param' => Param::class, + 'property-read' => PropertyRead::class, + 'property' => Property::class, + 'property-write' => PropertyWrite::class, + 'return' => Return_::class, + 'see' => SeeTag::class, + 'since' => Since::class, + 'source' => Source::class, + 'throw' => Throws::class, + 'throws' => Throws::class, + 'uses' => Uses::class, + 'var' => Var_::class, + 'version' => Version::class, + ]; + + /** + * @var array> An array with a anotation s a key, and an + * FQCN to a class that handles it as an array value. + */ + private $annotationMappings = []; + + /** + * @var ReflectionParameter[][] a lazy-loading cache containing parameters + * for each tagHandler that has been used. + */ + private $tagHandlerParameterCache = []; + + /** @var FqsenResolver */ + private $fqsenResolver; + + /** + * @var mixed[] an array representing a simple Service Locator where we can store parameters and + * services that can be inserted into the Factory Methods of Tag Handlers. + */ + private $serviceLocator = []; + + /** + * Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers. + * + * If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property + * is used. + * + * @see self::registerTagHandler() to add a new tag handler to the existing default list. + * + * @param array> $tagHandlers + */ + public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers = null) + { + $this->fqsenResolver = $fqsenResolver; + if ($tagHandlers !== null) { + $this->tagHandlerMappings = $tagHandlers; + } + + $this->addService($fqsenResolver, FqsenResolver::class); + } + + public function create(string $tagLine, ?TypeContext $context = null): Tag + { + if (!$context) { + $context = new TypeContext(''); + } + + [$tagName, $tagBody] = $this->extractTagParts($tagLine); + + return $this->createTag(trim($tagBody), $tagName, $context); + } + + /** + * @param mixed $value + */ + public function addParameter(string $name, $value): void + { + $this->serviceLocator[$name] = $value; + } + + public function addService(object $service, ?string $alias = null): void + { + $this->serviceLocator[$alias ?: get_class($service)] = $service; + } + + public function registerTagHandler(string $tagName, string $handler): void + { + Assert::stringNotEmpty($tagName); + Assert::classExists($handler); + Assert::implementsInterface($handler, Tag::class); + + if (strpos($tagName, '\\') && $tagName[0] !== '\\') { + throw new InvalidArgumentException( + 'A namespaced tag must have a leading backslash as it must be fully qualified' + ); + } + + $this->tagHandlerMappings[$tagName] = $handler; + } + + /** + * Extracts all components for a tag. + * + * @return string[] + */ + private function extractTagParts(string $tagLine): array + { + $matches = []; + if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\s\(\{])\s*([^\s].*)|$)/us', $tagLine, $matches)) { + throw new InvalidArgumentException( + 'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors' + ); + } + + if (count($matches) < 3) { + $matches[] = ''; + } + + return array_slice($matches, 1); + } + + /** + * Creates a new tag object with the given name and body or returns null if the tag name was recognized but the + * body was invalid. + */ + private function createTag(string $body, string $name, TypeContext $context): Tag + { + $handlerClassName = $this->findHandlerClassName($name, $context); + $arguments = $this->getArgumentsForParametersFromWiring( + $this->fetchParametersForHandlerFactoryMethod($handlerClassName), + $this->getServiceLocatorWithDynamicParameters($context, $name, $body) + ); + + try { + $callable = [$handlerClassName, 'create']; + Assert::isCallable($callable); + /** @phpstan-var callable(string): ?Tag $callable */ + $tag = call_user_func_array($callable, $arguments); + + return $tag ?? InvalidTag::create($body, $name); + } catch (InvalidArgumentException $e) { + return InvalidTag::create($body, $name)->withError($e); + } + } + + /** + * Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`). + * + * @return class-string + */ + private function findHandlerClassName(string $tagName, TypeContext $context): string + { + $handlerClassName = Generic::class; + if (isset($this->tagHandlerMappings[$tagName])) { + $handlerClassName = $this->tagHandlerMappings[$tagName]; + } elseif ($this->isAnnotation($tagName)) { + // TODO: Annotation support is planned for a later stage and as such is disabled for now + $tagName = (string) $this->fqsenResolver->resolve($tagName, $context); + if (isset($this->annotationMappings[$tagName])) { + $handlerClassName = $this->annotationMappings[$tagName]; + } + } + + return $handlerClassName; + } + + /** + * Retrieves the arguments that need to be passed to the Factory Method with the given Parameters. + * + * @param ReflectionParameter[] $parameters + * @param mixed[] $locator + * + * @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters + * is provided with this method. + */ + private function getArgumentsForParametersFromWiring(array $parameters, array $locator): array + { + $arguments = []; + foreach ($parameters as $parameter) { + $type = $parameter->getType(); + $typeHint = null; + if ($type instanceof ReflectionNamedType) { + $typeHint = $type->getName(); + if ($typeHint === 'self') { + $declaringClass = $parameter->getDeclaringClass(); + if ($declaringClass !== null) { + $typeHint = $declaringClass->getName(); + } + } + } + + if (isset($locator[$typeHint])) { + $arguments[] = $locator[$typeHint]; + continue; + } + + $parameterName = $parameter->getName(); + if (isset($locator[$parameterName])) { + $arguments[] = $locator[$parameterName]; + continue; + } + + $arguments[] = null; + } + + return $arguments; + } + + /** + * Retrieves a series of ReflectionParameter objects for the static 'create' method of the given + * tag handler class name. + * + * @param class-string $handlerClassName + * + * @return ReflectionParameter[] + */ + private function fetchParametersForHandlerFactoryMethod(string $handlerClassName): array + { + if (!isset($this->tagHandlerParameterCache[$handlerClassName])) { + $methodReflection = new ReflectionMethod($handlerClassName, 'create'); + $this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters(); + } + + return $this->tagHandlerParameterCache[$handlerClassName]; + } + + /** + * Returns a copy of this class' Service Locator with added dynamic parameters, + * such as the tag's name, body and Context. + * + * @param TypeContext $context The Context (namespace and aliasses) that may be + * passed and is used to resolve FQSENs. + * @param string $tagName The name of the tag that may be + * passed onto the factory method of the Tag class. + * @param string $tagBody The body of the tag that may be + * passed onto the factory method of the Tag class. + * + * @return mixed[] + */ + private function getServiceLocatorWithDynamicParameters( + TypeContext $context, + string $tagName, + string $tagBody + ): array { + return array_merge( + $this->serviceLocator, + [ + 'name' => $tagName, + 'body' => $tagBody, + TypeContext::class => $context, + ] + ); + } + + /** + * Returns whether the given tag belongs to an annotation. + * + * @todo this method should be populated once we implement Annotation notation support. + */ + private function isAnnotation(string $tagContent): bool + { + // 1. Contains a namespace separator + // 2. Contains parenthesis + // 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part + // of the annotation class name matches the found tag name + + return false; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php new file mode 100644 index 000000000..7cf07b4dd --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php @@ -0,0 +1,31 @@ + $handler FQCN of handler. + * + * @throws InvalidArgumentException If the tag name is not a string. + * @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but + * does not start with a backslash. + * @throws InvalidArgumentException If the handler is not a string. + * @throws InvalidArgumentException If the handler is not an existing class. + * @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface. + */ + public function registerTagHandler(string $tagName, string $handler): void; +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php new file mode 100644 index 000000000..ae09ecf42 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.php @@ -0,0 +1,102 @@ +authorName = $authorName; + $this->authorEmail = $authorEmail; + } + + /** + * Gets the author's name. + * + * @return string The author's name. + */ + public function getAuthorName(): string + { + return $this->authorName; + } + + /** + * Returns the author's email. + * + * @return string The author's email. + */ + public function getEmail(): string + { + return $this->authorEmail; + } + + /** + * Returns this tag in string form. + */ + public function __toString(): string + { + if ($this->authorEmail) { + $authorEmail = '<' . $this->authorEmail . '>'; + } else { + $authorEmail = ''; + } + + $authorName = $this->authorName; + + return $authorName . ($authorEmail !== '' ? ($authorName !== '' ? ' ' : '') . $authorEmail : ''); + } + + /** + * Attempts to create a new Author object based on the tag body. + */ + public static function create(string $body): ?self + { + $splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches); + if (!$splitTagContent) { + return null; + } + + $authorName = trim($matches[1]); + $email = isset($matches[2]) ? trim($matches[2]) : ''; + + return new static($authorName, $email); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php new file mode 100644 index 000000000..a28d5bf98 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php @@ -0,0 +1,53 @@ +name; + } + + public function getDescription(): ?Description + { + return $this->description; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php new file mode 100644 index 000000000..3eff9d8bc --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php @@ -0,0 +1,101 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?FqsenResolver $resolver = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + Assert::notNull($resolver); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference(): Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php new file mode 100644 index 000000000..dbcad28c0 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php @@ -0,0 +1,109 @@ +version = $version; + $this->description = $description; + } + + /** + * @return static + */ + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return new static( + null, + $descriptionFactory !== null ? $descriptionFactory->create($body, $context) : null + ); + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php new file mode 100644 index 000000000..825355aaf --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php @@ -0,0 +1,200 @@ +filePath = $filePath; + $this->startingLine = $startingLine; + $this->lineCount = $lineCount; + if ($content !== null) { + $this->content = trim($content); + } + + $this->isURI = $isURI; + } + + public function getContent(): string + { + if ($this->content === null || $this->content === '') { + $filePath = $this->filePath; + if ($this->isURI) { + $filePath = $this->isUriRelative($this->filePath) + ? str_replace('%2F', '/', rawurlencode($this->filePath)) + : $this->filePath; + } + + return trim($filePath); + } + + return $this->content; + } + + public function getDescription(): ?string + { + return $this->content; + } + + public static function create(string $body): ?Tag + { + // File component: File path in quotes or File URI / Source information + if (!preg_match('/^\s*(?:(\"[^\"]+\")|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) { + return null; + } + + $filePath = null; + $fileUri = null; + if ($matches[1] !== '') { + $filePath = $matches[1]; + } else { + $fileUri = $matches[2]; + } + + $startingLine = 1; + $lineCount = 0; + $description = null; + + if (array_key_exists(3, $matches)) { + $description = $matches[3]; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) { + $startingLine = (int) $contentMatches[1]; + if (isset($contentMatches[2])) { + $lineCount = (int) $contentMatches[2]; + } + + if (array_key_exists(3, $contentMatches)) { + $description = $contentMatches[3]; + } + } + } + + return new static( + $filePath ?? ($fileUri ?? ''), + $fileUri !== null, + $startingLine, + $lineCount, + $description + ); + } + + /** + * Returns the file path. + * + * @return string Path to a file to use as an example. + * May also be an absolute URI. + */ + public function getFilePath(): string + { + return trim($this->filePath, '"'); + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + $filePath = $this->filePath; + $isDefaultLine = $this->startingLine === 1 && $this->lineCount === 0; + $startingLine = !$isDefaultLine ? (string) $this->startingLine : ''; + $lineCount = !$isDefaultLine ? (string) $this->lineCount : ''; + $content = (string) $this->content; + + return $filePath + . ($startingLine !== '' + ? ($filePath !== '' ? ' ' : '') . $startingLine + : '') + . ($lineCount !== '' + ? ($filePath !== '' || $startingLine !== '' ? ' ' : '') . $lineCount + : '') + . ($content !== '' + ? ($filePath !== '' || $startingLine !== '' || $lineCount !== '' ? ' ' : '') . $content + : ''); + } + + /** + * Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute). + */ + private function isUriRelative(string $uri): bool + { + return strpos($uri, ':') === false; + } + + public function getStartingLine(): int + { + return $this->startingLine; + } + + public function getLineCount(): int + { + return $this->lineCount; + } + + public function getName(): string + { + return 'example'; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php new file mode 100644 index 000000000..f6f0bb5a4 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.php @@ -0,0 +1,25 @@ +maxLen = max($this->maxLen, strlen($tag->getName())); + } + } + + /** + * Formats the given tag to return a simple plain text version. + */ + public function format(Tag $tag): string + { + return '@' . $tag->getName() . + str_repeat( + ' ', + $this->maxLen - strlen($tag->getName()) + 1 + ) . + $tag; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php new file mode 100644 index 000000000..2afdfe55d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php @@ -0,0 +1,30 @@ +getName() . ' ' . $tag); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php new file mode 100644 index 000000000..bc1ab10c1 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php @@ -0,0 +1,89 @@ +validateTagName($name); + + $this->name = $name; + $this->description = $description; + } + + /** + * Creates a new tag that represents any unknown tag type. + * + * @return static + */ + public static function create( + string $body, + string $name = '', + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($name); + Assert::notNull($descriptionFactory); + + $description = $body !== '' ? $descriptionFactory->create($body, $context) : null; + + return new static($name, $description); + } + + /** + * Returns the tag as a serialized string + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + return $description; + } + + /** + * Validates if the tag name matches the expected format, otherwise throws an exception. + */ + private function validateTagName(string $name): void + { + if (!preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) { + throw new InvalidArgumentException( + 'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, ' + . 'hyphens and backslashes.' + ); + } + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php new file mode 100644 index 000000000..4e6abb8c4 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.php @@ -0,0 +1,145 @@ +name = $name; + $this->body = $body; + } + + public function getException(): ?Throwable + { + return $this->throwable; + } + + public function getName(): string + { + return $this->name; + } + + public static function create(string $body, string $name = ''): self + { + return new self($name, $body); + } + + public function withError(Throwable $exception): self + { + $this->flattenExceptionBacktrace($exception); + $tag = new self($this->name, $this->body); + $tag->throwable = $exception; + + return $tag; + } + + /** + * Removes all complex types from backtrace + * + * Not all objects are serializable. So we need to remove them from the + * stored exception to be sure that we do not break existing library usage. + */ + private function flattenExceptionBacktrace(Throwable $exception): void + { + $traceProperty = (new ReflectionClass(Exception::class))->getProperty('trace'); + $traceProperty->setAccessible(true); + + do { + $trace = $exception->getTrace(); + if (isset($trace[0]['args'])) { + $trace = array_map( + function (array $call): array { + $call['args'] = array_map([$this, 'flattenArguments'], $call['args'] ?? []); + + return $call; + }, + $trace + ); + } + + $traceProperty->setValue($exception, $trace); + $exception = $exception->getPrevious(); + } while ($exception !== null); + + $traceProperty->setAccessible(false); + } + + /** + * @param mixed $value + * + * @return mixed + * + * @throws ReflectionException + */ + private function flattenArguments($value) + { + if ($value instanceof Closure) { + $closureReflection = new ReflectionFunction($value); + $value = sprintf( + '(Closure at %s:%s)', + $closureReflection->getFileName(), + $closureReflection->getStartLine() + ); + } elseif (is_object($value)) { + $value = sprintf('object(%s)', get_class($value)); + } elseif (is_resource($value)) { + $value = sprintf('resource(%s)', get_resource_type($value)); + } elseif (is_array($value)) { + $value = array_map([$this, 'flattenArguments'], $value); + } + + return $value; + } + + public function render(?Formatter $formatter = null): string + { + if ($formatter === null) { + $formatter = new Formatter\PassthroughFormatter(); + } + + return $formatter->format($this); + } + + public function __toString(): string + { + return $this->body; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php new file mode 100644 index 000000000..ee242e3b2 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php @@ -0,0 +1,78 @@ +link = $link; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + return new static($parts[0], $description); + } + + /** + * Gets the link + */ + public function getLink(): string + { + return $this->link; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $link = $this->link; + + return $link . ($description !== '' ? ($link !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php new file mode 100644 index 000000000..f08bfffda --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php @@ -0,0 +1,279 @@ + + * @var array> + */ + private $arguments; + + /** @var bool */ + private $isStatic; + + /** @var Type */ + private $returnType; + + /** + * @param array> $arguments + * @phpstan-param array $arguments + */ + public function __construct( + string $methodName, + array $arguments = [], + ?Type $returnType = null, + bool $static = false, + ?Description $description = null + ) { + Assert::stringNotEmpty($methodName); + + if ($returnType === null) { + $returnType = new Void_(); + } + + $this->methodName = $methodName; + $this->arguments = $this->filterArguments($arguments); + $this->returnType = $returnType; + $this->isStatic = $static; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + // 1. none or more whitespace + // 2. optionally the keyword "static" followed by whitespace + // 3. optionally a word with underscores followed by whitespace : as + // type for the return value + // 4. then optionally a word with underscores followed by () and + // whitespace : as method name as used by phpDocumentor + // 5. then a word with underscores, followed by ( and any character + // until a ) and whitespace : as method name with signature + // 6. any remaining text : as description + if ( + !preg_match( + '/^ + # Static keyword + # Declares a static method ONLY if type is also present + (?: + (static) + \s+ + )? + # Return type + (?: + ( + (?:[\w\|_\\\\]*\$this[\w\|_\\\\]*) + | + (?: + (?:[\w\|_\\\\]+) + # array notation + (?:\[\])* + )*+ + ) + \s+ + )? + # Method name + ([\w_]+) + # Arguments + (?: + \(([^\)]*)\) + )? + \s* + # Description + (.*) + $/sux', + $body, + $matches + ) + ) { + return null; + } + + [, $static, $returnType, $methodName, $argumentLines, $description] = $matches; + + $static = $static === 'static'; + + if ($returnType === '') { + $returnType = 'void'; + } + + $returnType = $typeResolver->resolve($returnType, $context); + $description = $descriptionFactory->create($description, $context); + + /** @phpstan-var array $arguments */ + $arguments = []; + if ($argumentLines !== '') { + $argumentsExploded = explode(',', $argumentLines); + foreach ($argumentsExploded as $argument) { + $argument = explode(' ', self::stripRestArg(trim($argument)), 2); + if (strpos($argument[0], '$') === 0) { + $argumentName = substr($argument[0], 1); + $argumentType = new Mixed_(); + } else { + $argumentType = $typeResolver->resolve($argument[0], $context); + $argumentName = ''; + if (isset($argument[1])) { + $argument[1] = self::stripRestArg($argument[1]); + $argumentName = substr($argument[1], 1); + } + } + + $arguments[] = ['name' => $argumentName, 'type' => $argumentType]; + } + } + + return new static($methodName, $arguments, $returnType, $static, $description); + } + + /** + * Retrieves the method name. + */ + public function getMethodName(): string + { + return $this->methodName; + } + + /** + * @return array> + * @phpstan-return array + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * Checks whether the method tag describes a static method or not. + * + * @return bool TRUE if the method declaration is for a static method, FALSE otherwise. + */ + public function isStatic(): bool + { + return $this->isStatic; + } + + public function getReturnType(): Type + { + return $this->returnType; + } + + public function __toString(): string + { + $arguments = []; + foreach ($this->arguments as $argument) { + $arguments[] = $argument['type'] . ' $' . $argument['name']; + } + + $argumentStr = '(' . implode(', ', $arguments) . ')'; + + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $static = $this->isStatic ? 'static' : ''; + + $returnType = (string) $this->returnType; + + $methodName = $this->methodName; + + return $static + . ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '') + . ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $methodName : '') + . $argumentStr + . ($description !== '' ? ' ' . $description : ''); + } + + /** + * @param mixed[][]|string[] $arguments + * @phpstan-param array $arguments + * + * @return mixed[][] + * @phpstan-return array + */ + private function filterArguments(array $arguments = []): array + { + $result = []; + foreach ($arguments as $argument) { + if (is_string($argument)) { + $argument = ['name' => $argument]; + } + + if (!isset($argument['type'])) { + $argument['type'] = new Mixed_(); + } + + $keys = array_keys($argument); + sort($keys); + if ($keys !== ['name', 'type']) { + throw new InvalidArgumentException( + 'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true) + ); + } + + $result[] = $argument; + } + + return $result; + } + + private static function stripRestArg(string $argument): string + { + if (strpos($argument, '...') === 0) { + $argument = trim(substr($argument, 3)); + } + + return $argument; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php new file mode 100644 index 000000000..3399649b8 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.php @@ -0,0 +1,174 @@ +name = 'param'; + $this->variableName = $variableName; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->description = $description; + $this->isReference = $isReference; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + $isVariadic = false; + $isReference = false; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && !self::strStartsWithVariable($firstPart)) { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ or ...$ or &$ or &...$ it must be the variable name + if (isset($parts[0]) && self::strStartsWithVariable($parts[0])) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + if (strpos($variableName, '$') === 0) { + $variableName = substr($variableName, 1); + } elseif (strpos($variableName, '&$') === 0) { + $isReference = true; + $variableName = substr($variableName, 2); + } elseif (strpos($variableName, '...$') === 0) { + $isVariadic = true; + $variableName = substr($variableName, 4); + } elseif (strpos($variableName, '&...$') === 0) { + $isVariadic = true; + $isReference = true; + $variableName = substr($variableName, 5); + } + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $isVariadic, $description, $isReference); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns whether this tag is variadic. + */ + public function isVariadic(): bool + { + return $this->isVariadic; + } + + /** + * Returns whether this tag is passed by reference. + */ + public function isReference(): bool + { + return $this->isReference; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $variableName = ''; + if ($this->variableName) { + $variableName .= ($this->isReference ? '&' : '') . ($this->isVariadic ? '...' : ''); + $variableName .= '$' . $this->variableName; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } + + private static function strStartsWithVariable(string $str): bool + { + return strpos($str, '$') === 0 + || + strpos($str, '...$') === 0 + || + strpos($str, '&$') === 0 + || + strpos($str, '&...$') === 0; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php new file mode 100644 index 000000000..2521fb3f0 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.php @@ -0,0 +1,121 @@ +name = 'property'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php new file mode 100644 index 000000000..9491b39c3 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.php @@ -0,0 +1,121 @@ +name = 'property-read'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php new file mode 100644 index 000000000..2bfdac6a0 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php @@ -0,0 +1,121 @@ +name = 'property-write'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + $type = null; + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php new file mode 100644 index 000000000..532003dd8 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php @@ -0,0 +1,38 @@ +fqsen = $fqsen; + } + + /** + * @return string string representation of the referenced fqsen + */ + public function __toString(): string + { + return (string) $this->fqsen; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php new file mode 100644 index 000000000..e7dea868d --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.php @@ -0,0 +1,22 @@ +uri = $uri; + } + + public function __toString(): string + { + return $this->uri; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php new file mode 100644 index 000000000..f021b6092 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php @@ -0,0 +1,64 @@ +name = 'return'; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } + + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $type = $this->type ? '' . $this->type : 'mixed'; + + return $type . ($description !== '' ? ' ' . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php new file mode 100644 index 000000000..a194c7ded --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.php @@ -0,0 +1,106 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + $description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null; + + // https://tools.ietf.org/html/rfc2396#section-3 + if (preg_match('#\w://\w#', $parts[0])) { + return new static(new Url($parts[0]), $description); + } + + return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the ref of this tag. + */ + public function getReference(): Reference + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php new file mode 100644 index 000000000..54af43cd4 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.php @@ -0,0 +1,103 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + Assert::notNull($descriptionFactory); + + return new static( + $matches[1], + $descriptionFactory->create($matches[2] ?? '', $context) + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php new file mode 100644 index 000000000..8b8c0fb47 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php @@ -0,0 +1,116 @@ +startingLine = (int) $startingLine; + $this->lineCount = $lineCount !== null ? (int) $lineCount : null; + $this->description = $description; + } + + public static function create( + string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($descriptionFactory); + + $startingLine = 1; + $lineCount = null; + $description = null; + + // Starting line / Number of lines / Description + if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) { + $startingLine = (int) $matches[1]; + if (isset($matches[2]) && $matches[2] !== '') { + $lineCount = (int) $matches[2]; + } + + $description = $matches[3]; + } + + return new static($startingLine, $lineCount, $descriptionFactory->create($description ?? '', $context)); + } + + /** + * Gets the starting line. + * + * @return int The starting line, relative to the structural element's + * location. + */ + public function getStartingLine(): int + { + return $this->startingLine; + } + + /** + * Returns the number of lines. + * + * @return int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + public function getLineCount(): ?int + { + return $this->lineCount; + } + + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $startingLine = (string) $this->startingLine; + + $lineCount = $this->lineCount !== null ? ' ' . $this->lineCount : ''; + + return $startingLine + . $lineCount + . ($description !== '' + ? ' ' . $description + : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php new file mode 100644 index 000000000..158578bd2 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php @@ -0,0 +1,66 @@ +type; + } + + /** + * @return string[] + */ + protected static function extractTypeFromBody(string $body): array + { + $type = ''; + $nestingLevel = 0; + for ($i = 0, $iMax = strlen($body); $i < $iMax; $i++) { + $character = $body[$i]; + + if ($nestingLevel === 0 && trim($character) === '') { + break; + } + + $type .= $character; + if (in_array($character, ['<', '(', '[', '{'])) { + $nestingLevel++; + continue; + } + + if (in_array($character, ['>', ')', ']', '}'])) { + $nestingLevel--; + continue; + } + } + + $description = trim(substr($body, strlen($type))); + + return [$type, $description]; + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php new file mode 100644 index 000000000..f21c91011 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.php @@ -0,0 +1,64 @@ +name = 'throws'; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$type, $description] = self::extractTypeFromBody($body); + + $type = $typeResolver->resolve($type, $context); + $description = $descriptionFactory->create($description, $context); + + return new static($type, $description); + } + + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $type = (string) $this->type; + + return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php new file mode 100644 index 000000000..b72f40347 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.php @@ -0,0 +1,100 @@ +refers = $refers; + $this->description = $description; + } + + public static function create( + string $body, + ?FqsenResolver $resolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::notNull($resolver); + Assert::notNull($descriptionFactory); + + $parts = Utils::pregSplit('/\s+/Su', $body, 2); + + return new static( + self::resolveFqsen($parts[0], $resolver, $context), + $descriptionFactory->create($parts[1] ?? '', $context) + ); + } + + private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context): Fqsen + { + Assert::notNull($fqsenResolver); + $fqsenParts = explode('::', $parts); + $resolved = $fqsenResolver->resolve($fqsenParts[0], $context); + + if (!array_key_exists(1, $fqsenParts)) { + return $resolved; + } + + return new Fqsen($resolved . '::' . $fqsenParts[1]); + } + + /** + * Returns the structural element this tag refers to. + */ + public function getReference(): Fqsen + { + return $this->refers; + } + + /** + * Returns a string representation of this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $refers = (string) $this->refers; + + return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php new file mode 100644 index 000000000..fa1f9dbf6 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.php @@ -0,0 +1,122 @@ +name = 'var'; + $this->variableName = $variableName; + $this->type = $type; + $this->description = $description; + } + + public static function create( + string $body, + ?TypeResolver $typeResolver = null, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): self { + Assert::stringNotEmpty($body); + Assert::notNull($typeResolver); + Assert::notNull($descriptionFactory); + + [$firstPart, $body] = self::extractTypeFromBody($body); + + $parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE); + $type = null; + $variableName = ''; + + // if the first item that is encountered is not a variable; it is a type + if ($firstPart && $firstPart[0] !== '$') { + $type = $typeResolver->resolve($firstPart, $context); + } else { + // first part is not a type; we should prepend it to the parts array for further processing + array_unshift($parts, $firstPart); + } + + // if the next item starts with a $ it must be the variable name + if (isset($parts[0]) && strpos($parts[0], '$') === 0) { + $variableName = array_shift($parts); + if ($type) { + array_shift($parts); + } + + Assert::notNull($variableName); + + $variableName = substr($variableName, 1); + } + + $description = $descriptionFactory->create(implode('', $parts), $context); + + return new static($variableName, $type, $description); + } + + /** + * Returns the variable's name. + */ + public function getVariableName(): ?string + { + return $this->variableName; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + if ($this->variableName) { + $variableName = '$' . $this->variableName; + } else { + $variableName = ''; + } + + $type = (string) $this->type; + + return $type + . ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '') + . ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : ''); + } +} diff --git a/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php new file mode 100644 index 000000000..f46e4b8c0 --- /dev/null +++ b/vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.php @@ -0,0 +1,106 @@ +version = $version; + $this->description = $description; + } + + public static function create( + ?string $body, + ?DescriptionFactory $descriptionFactory = null, + ?TypeContext $context = null + ): ?self { + if (empty($body)) { + return new static(); + } + + $matches = []; + if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) { + return null; + } + + $description = null; + if ($descriptionFactory !== null) { + $description = $descriptionFactory->create($matches[2] ?? '', $context); + } + + return new static( + $matches[1], + $description + ); + } + + /** + * Gets the version section of the tag. + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * Returns a string representation for this tag. + */ + public function __toString(): string + { + if ($this->description) { + $description = $this->description->render(); + } else { + $description = ''; + } + + $version = (string) $this->version; + + return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : ''); + } +} -- cgit v1.2.3