project_start/phpstorm-stubs/tests/Model/PHPFunction.php

237 lines
9.0 KiB
PHP

<?php
namespace StubTests\Model;
use Exception;
use JetBrains\PhpStorm\Deprecated;
use JetBrains\PhpStorm\Internal\TentativeType;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
use PhpParser\Comment\Doc;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Function_;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use RuntimeException;
use stdClass;
use StubTests\Parsers\DocFactoryProvider;
class PHPFunction extends BasePHPElement
{
/**
* @var bool
*/
public $isDeprecated;
/**
* @var PHPParameter[]
*/
public $parameters = [];
/** @var string[] */
public $returnTypesFromPhpDoc = [];
/** @var string[][] */
public $returnTypesFromAttribute = [];
/** @var string[] */
public $returnTypesFromSignature = [];
public $hasTentativeReturnType = false;
/**
* @param ReflectionFunction|ReflectionFunctionAbstract $reflectionObject
* @return static
*/
public function readObjectFromReflection($reflectionObject)
{
$this->name = $reflectionObject->name;
$this->isDeprecated = $reflectionObject->isDeprecated();
foreach ($reflectionObject->getParameters() as $parameter) {
$this->parameters[] = (new PHPParameter())->readObjectFromReflection($parameter);
}
if (method_exists($reflectionObject, 'getReturnType')) {
$returnTypes = self::getReflectionTypeAsArray($reflectionObject->getReturnType());
}
if (!empty($returnTypes)) {
array_push($this->returnTypesFromSignature, ...$returnTypes);
}
return $this;
}
/**
* @param Function_ $node
* @return static
* @throws RuntimeException
*/
public function readObjectFromStubNode($node)
{
$functionName = self::getFQN($node);
$this->name = $functionName;
$typesFromAttribute = self::findTypesFromAttribute($node->attrGroups);
$this->availableVersionsRangeFromAttribute = self::findAvailableVersionsRangeFromAttribute($node->attrGroups);
$this->returnTypesFromAttribute = $typesFromAttribute;
array_push($this->returnTypesFromSignature, ...self::convertParsedTypeToArray($node->getReturnType()));
$index = 0;
foreach ($node->getParams() as $parameter) {
$parsedParameter = (new PHPParameter())->readObjectFromStubNode($parameter);
if (self::entitySuitsCurrentPhpVersion($parsedParameter)) {
$parsedParameter->indexInSignature = $index;
$addedParameters = array_filter($this->parameters, function (PHPParameter $addedParameter) use ($parsedParameter) {
return $addedParameter->name === $parsedParameter->name;
});
if (!empty($addedParameters)) {
if ($parsedParameter->is_vararg) {
$parsedParameter->isOptional = false;
$index--;
$parsedParameter->indexInSignature = $index;
}
}
$this->parameters[$parsedParameter->name] = $parsedParameter;
$index++;
}
}
$this->collectTags($node);
foreach ($this->parameters as $parameter) {
$relatedParamTags = array_filter($this->paramTags, function (Param $tag) use ($parameter) {
return $tag->getVariableName() === $parameter->name;
});
/** @var Param $relatedParamTag */
$relatedParamTag = array_pop($relatedParamTags);
if ($relatedParamTag !== null) {
$parameter->isOptional = $parameter->isOptional || str_contains((string)$relatedParamTag->getDescription(), '[optional]');
$parameter->markedOptionalInPhpDoc = str_contains((string)$relatedParamTag->getDescription(), '[optional]');
}
}
$this->checkIfReturnTypeIsTentative($node);
$this->checkDeprecationTag($node);
$this->checkReturnTag();
return $this;
}
protected function checkIfReturnTypeIsTentative(FunctionLike $node) {
$this->hasTentativeReturnType = self::hasTentativeReturnTypeAttribute($node);
}
protected function checkDeprecationTag(FunctionLike $node)
{
$this->isDeprecated = self::hasDeprecatedAttribute($node) || !empty($this->deprecatedTags);
}
protected function checkReturnTag()
{
if (!empty($this->returnTags) && $this->returnTags[0] instanceof Return_) {
$type = $this->returnTags[0]->getType();
if ($type instanceof Collection) {
$returnType = $type->getFqsen();
} elseif ($type instanceof Array_ && $type->getValueType() instanceof Collection) {
$returnType = "array";
} else {
$returnType = $type;
}
if ($returnType instanceof Compound) {
foreach ($returnType as $nextType) {
$this->returnTypesFromPhpDoc[] = (string)$nextType;
}
} else {
$this->returnTypesFromPhpDoc[] = (string)$returnType;
}
}
}
/**
* @param stdClass|array $jsonData
* @throws Exception
*/
public function readMutedProblems($jsonData)
{
foreach ($jsonData as $function) {
if ($function->name === $this->name) {
if (!empty($function->problems)) {
foreach ($function->problems as $problem) {
switch ($problem->description) {
case 'parameter mismatch':
$this->mutedProblems[StubProblemType::FUNCTION_PARAMETER_MISMATCH] = $problem->versions;
break;
case 'missing function':
$this->mutedProblems[StubProblemType::STUB_IS_MISSED] = $problem->versions;
break;
case 'deprecated function':
$this->mutedProblems[StubProblemType::FUNCTION_IS_DEPRECATED] = $problem->versions;
break;
case 'absent in meta':
$this->mutedProblems[StubProblemType::ABSENT_IN_META] = $problem->versions;
break;
case 'has return typehint':
$this->mutedProblems[StubProblemType::FUNCTION_HAS_RETURN_TYPEHINT] = $problem->versions;
break;
case 'wrong return typehint':
$this->mutedProblems[StubProblemType::WRONG_RETURN_TYPEHINT] = $problem->versions;
break;
case 'has duplicate in stubs':
$this->mutedProblems[StubProblemType::HAS_DUPLICATION] = $problem->versions;
break;
case 'has type mismatch in signature and phpdoc':
$this->mutedProblems[StubProblemType::TYPE_IN_PHPDOC_DIFFERS_FROM_SIGNATURE] = $problem->versions;
break;
default:
throw new Exception("Unexpected value $problem->description");
}
}
}
if (!empty($function->parameters)) {
foreach ($this->parameters as $parameter) {
$parameter->readMutedProblems($function->parameters);
}
}
}
}
}
/**
* @param FunctionLike $node
* @return bool
*/
private static function hasDeprecatedAttribute(FunctionLike $node)
{
foreach ($node->getAttrGroups() as $group) {
foreach ($group->attrs as $attr) {
if ((string)$attr->name === Deprecated::class) {
return true;
}
}
}
return false;
}
/**
* @param FunctionLike $node
* @return bool
*/
public static function hasTentativeReturnTypeAttribute(FunctionLike $node)
{
foreach ($node->getAttrGroups() as $group) {
foreach ($group->attrs as $attr) {
if ((string)$attr->name === TentativeType::class) {
return true;
}
}
}
return false;
}
/**
* @param Doc|null $docComment
* @return bool
*/
private static function hasDeprecatedDocTag($docComment)
{
$phpDoc = $docComment !== null ? DocFactoryProvider::getDocFactory()->create($docComment->getText()) : null;
return $phpDoc !== null && !empty($phpDoc->getTagsByName('deprecated'));
}
}