Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PhpStan based tag parsing #343

Merged
merged 16 commits into from
Nov 11, 2022
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
],
"require": {
"php": "^7.2 || ^8.0",
"phpdocumentor/type-resolver": "^1.3",
"phpdocumentor/type-resolver": "1.x-dev@dev",
"webmozart/assert": "^1.9.1",
"phpdocumentor/reflection-common": "^2.2",
"ext-filter": "*"
"ext-filter": "*",
"phpstan/phpdoc-parser": "^1.7"
},
"require-dev": {
"mockery/mockery": "~1.3.5",
Expand Down
77 changes: 66 additions & 11 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@

<!-- Set the minimum PHP version for PHPCompatibility.
This should be kept in sync with the requirements in the composer.json file. -->
<config name="testVersion" value="7.2-"/>
<config name="testVersion" value="7.4-"/>

<rule ref="phpDocumentor">
<exclude name="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly.ReferencedGeneralException" />

<!-- Property type declarations are a PHP 7.4 feature. -->
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint"/>
</rule>

<rule ref="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix">
Expand Down
4 changes: 2 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd"
colors="true"
convertDeprecationsToExceptions="true"
beStrictAboutOutputDuringTests="true"
convertDeprecationsToExceptions="false"
beStrictAboutOutputDuringTests="false"
forceCoversAnnotation="true"
verbose="true"
bootstrap="vendor/autoload.php"
Expand Down
5 changes: 3 additions & 2 deletions src/DocBlock/DescriptionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace phpDocumentor\Reflection\DocBlock;

use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;

Expand Down Expand Up @@ -47,13 +48,13 @@
*/
class DescriptionFactory
{
/** @var TagFactory */
/** @var Factory */
private $tagFactory;

/**
* Initializes this factory with the means to construct (inline) tags.
*/
public function __construct(TagFactory $tagFactory)
public function __construct(Factory $tagFactory)
{
$this->tagFactory = $tagFactory;
}
Expand Down
43 changes: 30 additions & 13 deletions src/DocBlock/StandardTagFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use phpDocumentor\Reflection\DocBlock\Tags\Author;
use phpDocumentor\Reflection\DocBlock\Tags\Covers;
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag;
Expand All @@ -40,12 +41,15 @@
use ReflectionParameter;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_merge;
use function array_slice;
use function call_user_func_array;
use function count;
use function get_class;
use function is_object;
use function preg_match;
use function sprintf;
use function strpos;
use function trim;

Expand All @@ -72,7 +76,7 @@ final class StandardTagFactory implements TagFactory
public const REGEX_TAGNAME = '[\w\-\_\\\\:]+';

/**
* @var array<class-string<Tag>> An array with a tag as a key, and an
* @var array<class-string<Tag>|Factory> An array with a tag as a key, and an
* FQCN to a class that handles it as an array value.
*/
private $tagHandlerMappings = [
Expand Down Expand Up @@ -162,18 +166,25 @@ public function addService(object $service, ?string $alias = null): void
$this->serviceLocator[$alias ?: get_class($service)] = $service;
}

public function registerTagHandler(string $tagName, string $handler): void
/** {@inheritDoc} */
public function registerTagHandler(string $tagName, $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'
);
}

if (is_object($handler)) {
Assert::isInstanceOf($handler, Factory::class);
$this->tagHandlerMappings[$tagName] = $handler;

return;
}

Assert::classExists($handler);
Assert::implementsInterface($handler, Tag::class);
$this->tagHandlerMappings[$tagName] = $handler;
}

Expand Down Expand Up @@ -210,6 +221,10 @@ private function createTag(string $body, string $name, TypeContext $context): Ta
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
);

if (array_key_exists('tagLine', $arguments)) {
$arguments['tagLine'] = sprintf('@%s %s', $name, $body);
}

try {
$callable = [$handlerClassName, 'create'];
Assert::isCallable($callable);
Expand All @@ -225,9 +240,9 @@ private function createTag(string $body, string $name, TypeContext $context): Ta
/**
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
*
* @return class-string<Tag>
* @return class-string<Tag>|Factory
*/
private function findHandlerClassName(string $tagName, TypeContext $context): string
private function findHandlerClassName(string $tagName, TypeContext $context)
{
$handlerClassName = Generic::class;
if (isset($this->tagHandlerMappings[$tagName])) {
Expand Down Expand Up @@ -268,18 +283,18 @@ private function getArgumentsForParametersFromWiring(array $parameters, array $l
}
}

$parameterName = $parameter->getName();
if (isset($locator[$typeHint])) {
$arguments[] = $locator[$typeHint];
$arguments[$parameterName] = $locator[$typeHint];
continue;
}

$parameterName = $parameter->getName();
if (isset($locator[$parameterName])) {
$arguments[] = $locator[$parameterName];
$arguments[$parameterName] = $locator[$parameterName];
continue;
}

$arguments[] = null;
$arguments[$parameterName] = null;
}

return $arguments;
Expand All @@ -289,12 +304,14 @@ private function getArgumentsForParametersFromWiring(array $parameters, array $l
* Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
* tag handler class name.
*
* @param class-string $handlerClassName
* @param class-string|Factory $handler
*
* @return ReflectionParameter[]
*/
private function fetchParametersForHandlerFactoryMethod(string $handlerClassName): array
private function fetchParametersForHandlerFactoryMethod($handler): array
{
$handlerClassName = is_object($handler) ? get_class($handler) : $handler;

if (!isset($this->tagHandlerParameterCache[$handlerClassName])) {
$methodReflection = new ReflectionMethod($handlerClassName, 'create');
$this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
Expand Down
19 changes: 4 additions & 15 deletions src/DocBlock/TagFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
namespace phpDocumentor\Reflection\DocBlock;

use InvalidArgumentException;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;

interface TagFactory
interface TagFactory extends Factory
{
/**
* Adds a parameter to the service locator that can be injected in a tag's factory method.
Expand All @@ -40,17 +40,6 @@ interface TagFactory
*/
public function addParameter(string $name, $value): void;

/**
* Factory method responsible for instantiating the correct sub type.
*
* @param string $tagLine The text for this tag, including description.
*
* @return Tag A new tag object.
*
* @throws InvalidArgumentException If an invalid tag line was presented.
*/
public function create(string $tagLine, ?TypeContext $context = null): Tag;

/**
* Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.
*
Expand All @@ -71,7 +60,7 @@ public function addService(object $service): void;
*
* @param string $tagName Name of tag to register a handler for. When registering a namespaced
* tag, the full name, along with a prefixing slash MUST be provided.
* @param class-string<Tag> $handler FQCN of handler.
* @param class-string<Tag>|Factory $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
Expand All @@ -80,5 +69,5 @@ public function addService(object $service): void;
* @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;
public function registerTagHandler(string $tagName, $handler): void;
}
Loading