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

Request validation rules documentation based on static analysis instead of eval #237

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fcf8d54
feat: class based rules api
romalytvynenko Sep 24, 2023
a967b2b
feat: ability to get type of any node from a method (maybe to be chan…
romalytvynenko Sep 24, 2023
b498847
feat: added concated strings type
romalytvynenko Sep 24, 2023
91024ef
feat: static methods call support
romalytvynenko Sep 26, 2023
f20ef4a
Fix styling
romalytvynenko Sep 26, 2023
3db75bb
feat: late static binding, class const fetch
romalytvynenko Sep 27, 2023
6731198
Fix styling
romalytvynenko Sep 27, 2023
5ed1fc6
feat: added keywords support to new call
romalytvynenko Sep 27, 2023
3aed7a7
feat: improved default prop types inference
romalytvynenko Sep 27, 2023
75bad2d
feat: if template type has some constraints, they will be used to det…
romalytvynenko Sep 27, 2023
c539639
Fix styling
romalytvynenko Sep 27, 2023
9f48d7d
test: test parameters type retrieval
romalytvynenko Sep 27, 2023
64250df
Fix styling
romalytvynenko Sep 27, 2023
7104b0a
feat: static methods and prop fetch support
romalytvynenko Sep 28, 2023
7cd7c42
Fix styling
romalytvynenko Sep 28, 2023
cd65e6d
feat: array_keys support
romalytvynenko Oct 6, 2023
34a3a92
wip
romalytvynenko Dec 2, 2023
4368d7b
Fix styling
romalytvynenko Dec 2, 2023
c648396
Merge branch 'main' into 226-docs-generation-fails-for-requests-that-…
romalytvynenko Mar 4, 2024
14290a9
Fix styling
romalytvynenko Mar 4, 2024
8f742b0
fix attempt of making non-instantiable model
romalytvynenko Mar 4, 2024
b4755e0
Merge branch '226-docs-generation-fails-for-requests-that-utilise-cus…
romalytvynenko Mar 4, 2024
215b30f
Merge branch 'main' into 226-docs-generation-fails-for-requests-that-…
romalytvynenko Mar 31, 2024
a71b627
fix tests
romalytvynenko Mar 31, 2024
ba2e1d1
Fix styling
romalytvynenko Mar 31, 2024
f69af67
Merge branch 'main' into 226-docs-generation-fails-for-requests-that-…
romalytvynenko May 20, 2024
f7ab03e
resolving default
romalytvynenko May 20, 2024
c785fb8
Merge branch 'main' into 226-docs-generation-fails-for-requests-that-…
romalytvynenko May 24, 2024
9567450
Merge branch 'main' into 226-docs-generation-fails-for-requests-that-…
romalytvynenko May 24, 2024
3d46311
Fix styling
romalytvynenko May 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Infer/Analyzer/MethodAnalysisResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Dedoc\Scramble\Infer\Analyzer;

use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition;
use Dedoc\Scramble\Infer\Scope\Scope;

class MethodAnalysisResult
{
public function __construct(
public Scope $scope,
public FunctionLikeDefinition $definition,
) {
}
}
11 changes: 7 additions & 4 deletions src/Infer/Analyzer/MethodAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function __construct(

public function analyze(FunctionLikeDefinition $methodDefinition, array $indexBuilders = [])
{
$this->traverseClassMethod(
$scope = $this->traverseClassMethod(
[$this->getClassReflector()->getMethod($methodDefinition->type->name)->getAstNode()],
$methodDefinition,
$indexBuilders,
Expand All @@ -40,7 +40,10 @@ public function analyze(FunctionLikeDefinition $methodDefinition, array $indexBu

$methodDefinition->isFullyAnalyzed = true;

return $methodDefinition;
return new MethodAnalysisResult(
scope: $scope,
definition: $methodDefinition,
);
}

private function getClassReflector(): ClassReflector
Expand All @@ -57,7 +60,7 @@ private function traverseClassMethod(array $nodes, FunctionLikeDefinition $metho
$traverser->addVisitor(new TypeInferer(
$this->index,
$nameResolver,
new Scope($this->index, new NodeTypesResolver(), new ScopeContext($this->classDefinition), $nameResolver),
$scope = new Scope($this->index, new NodeTypesResolver(), new ScopeContext($this->classDefinition), $nameResolver),
Context::getInstance()->extensionsBroker->extensions,
[new IndexBuildingHandler($indexBuilders)],
));
Expand All @@ -70,6 +73,6 @@ private function traverseClassMethod(array $nodes, FunctionLikeDefinition $metho

$traverser->traverse(Arr::wrap($node));

return $node;
return $scope;
}
}
14 changes: 13 additions & 1 deletion src/Infer/Definition/ClassDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

class ClassDefinition
{
private array $methodsScopes = [];

public function __construct(
// FQ name
public string $name,
Expand Down Expand Up @@ -57,10 +59,13 @@ public function getMethodDefinition(string $name, Scope $scope = new GlobalScope
$methodDefinition = $this->methods[$name];

if (! $methodDefinition->isFullyAnalyzed()) {
$this->methods[$name] = (new MethodAnalyzer(
$result = (new MethodAnalyzer(
$scope->index,
$this
))->analyze($methodDefinition, $indexBuilders);

$this->methodsScopes[$name] = $result->scope;
$this->methods[$name] = $result->definition;
}

$methodScope = new Scope(
Expand Down Expand Up @@ -116,4 +121,11 @@ private function replaceTemplateInType(Type $type, array $templateTypesMap)

return $type;
}

public function getMethodScope(string $methodName)
{
$this->getMethodDefinition($methodName);

return $this->methodsScopes[$methodName] ?? null;
}
}
28 changes: 28 additions & 0 deletions src/Infer/Extensions/Event/FunctionCallEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Dedoc\Scramble\Infer\Extensions\Event;

use Dedoc\Scramble\Infer\Extensions\Event\Concerns\ArgumentTypesAware;
use Dedoc\Scramble\Infer\Scope\Scope;

class FunctionCallEvent
{
use ArgumentTypesAware;

public function __construct(
public readonly string $name,
public readonly Scope $scope,
public readonly array $arguments,
) {
}

public function getDefinition()
{
return $this->scope->index->getFunctionDefinition($this->name);
}

public function getName()
{
return $this->name;
}
}
16 changes: 16 additions & 0 deletions src/Infer/Extensions/ExtensionsBroker.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ public function getMethodReturnType($event)
return null;
}

public function getFunctionReturnType($event)
{
$extensions = array_filter($this->extensions, function ($e) use ($event) {
return $e instanceof FunctionReturnTypeExtension
&& $e->shouldHandle($event->getName());
});

foreach ($extensions as $extension) {
if ($propertyType = $extension->getFunctionReturnType($event)) {
return $propertyType;
}
}

return null;
}

public function getStaticMethodReturnType($event)
{
$extensions = array_filter($this->extensions, function ($e) use ($event) {
Expand Down
13 changes: 13 additions & 0 deletions src/Infer/Extensions/FunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Dedoc\Scramble\Infer\Extensions;

use Dedoc\Scramble\Infer\Extensions\Event\FunctionCallEvent;
use Dedoc\Scramble\Support\Type\Type;

interface FunctionReturnTypeExtension extends InferExtension
{
public function shouldHandle(string $name): bool;

public function getFunctionReturnType(FunctionCallEvent $event): ?Type;
}
27 changes: 27 additions & 0 deletions src/Infer/Handler/ConcatHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Dedoc\Scramble\Infer\Handler;

use Dedoc\Scramble\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Type\ConcatenatedStringType;
use Dedoc\Scramble\Support\Type\TypeHelper;
use PhpParser\Node;

class ConcatHandler
{
public function shouldHandle($node)
{
return $node instanceof Node\Expr\BinaryOp\Concat;
}

public function leave(Node\Expr\BinaryOp\Concat $node, Scope $scope)
{
$scope->setType(
$node,
new ConcatenatedStringType(TypeHelper::flattenStringConcatTypes([
$scope->getType($node->left),
$scope->getType($node->right),
])),
);
}
}
50 changes: 38 additions & 12 deletions src/Infer/Scope/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
use Dedoc\Scramble\Support\Type\CallableStringType;
use Dedoc\Scramble\Support\Type\KeyedArrayType;
use Dedoc\Scramble\Support\Type\ObjectType;
use Dedoc\Scramble\Support\Type\Reference\AbstractReferenceType;
use Dedoc\Scramble\Support\Type\Reference\CallableCallReferenceType;
use Dedoc\Scramble\Support\Type\Reference\MethodCallReferenceType;
use Dedoc\Scramble\Support\Type\Reference\NewCallReferenceType;
use Dedoc\Scramble\Support\Type\Reference\PropertyFetchReferenceType;
use Dedoc\Scramble\Support\Type\Reference\StaticMethodCallReferenceType;
use Dedoc\Scramble\Support\Type\Reference\StaticPropertyFetchReferenceType;
use Dedoc\Scramble\Support\Type\SelfType;
use Dedoc\Scramble\Support\Type\TemplateType;
use Dedoc\Scramble\Support\Type\Type;
Expand Down Expand Up @@ -54,7 +56,13 @@ public function getType(Node $node): Type
}

if ($node instanceof Node\Expr\ConstFetch) {
return (new ConstFetchTypeGetter)($node);
$type = (new ConstFetchTypeGetter)($node);

if (! $type instanceof AbstractReferenceType) {
return $type;
}

return $this->setType($node, $type);
}

if ($node instanceof Node\Expr\Ternary) {
Expand All @@ -73,7 +81,13 @@ public function getType(Node $node): Type
}

if ($node instanceof Node\Expr\ClassConstFetch) {
return (new ClassConstFetchTypeGetter)($node, $this);
$type = (new ClassConstFetchTypeGetter)($node, $this);

if (! $type instanceof AbstractReferenceType) {
return $type;
}

return $this->setType($node, $type);
}

if ($node instanceof Node\Expr\BooleanNot) {
Expand Down Expand Up @@ -117,11 +131,11 @@ public function getType(Node $node): Type

$calleeType = $this->getType($node->var);
if ($calleeType instanceof TemplateType) {
// @todo
// if ($calleeType->is instanceof ObjectType) {
// $calleeType = $calleeType->is;
// }
return $this->setType($node, new UnknownType("Cannot infer type of method [{$node->name->name}] call on template type: not supported yet."));
if ($calleeType->is instanceof ObjectType) {
$calleeType = $calleeType->is;
} else {
return $this->setType($node, new UnknownType("Cannot infer type of method [{$node->name->name}] call on template type: not supported yet."));
}
}

return $this->setType(
Expand All @@ -147,6 +161,23 @@ public function getType(Node $node): Type
);
}

if ($node instanceof Node\Expr\StaticPropertyFetch) {
// Only string method names support.
if (! $node->name instanceof Node\Identifier) {
return $type;
}

// Only string class names support.
if (! $node->class instanceof Node\Name) {
return $type;
}

return $this->setType(
$node,
new StaticPropertyFetchReferenceType($node->class->toString(), $node->name->name),
);
}

if ($node instanceof Node\Expr\PropertyFetch) {
// Only string prop names support.
if (! $name = ($node->name->name ?? null)) {
Expand Down Expand Up @@ -303,11 +334,6 @@ private function getVariableType(Node\Expr\Variable $node)
return $type;
}

public function getMethodCallType(Type $calledOn, string $methodName, array $arguments = []): Type
{

}

public function getPropertyFetchType(Type $calledOn, string $propertyName): Type
{
return (new ReferenceTypeResolver($this->index))->resolve($this, new PropertyFetchReferenceType($calledOn, $propertyName));
Expand Down
27 changes: 27 additions & 0 deletions src/Infer/Scope/ScopeTypeResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Dedoc\Scramble\Infer\Scope;

use Dedoc\Scramble\Infer\Services\ReferenceTypeResolver;
use Dedoc\Scramble\Support\Type\Type;
use PhpParser\Node;

class ScopeTypeResolver
{
public function __construct(private Scope $scope)
{
}

public function getType(Node $node): ?Type
{
$type = $this->scope->getType($node);

if (! $type) {
return null;
}

return (new ReferenceTypeResolver($this->scope->index))
->resolve($this->scope, $type)
->mergeAttributes($type->attributes());
}
}
33 changes: 33 additions & 0 deletions src/Infer/Services/ConstFetchTypeGetter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Dedoc\Scramble\Infer\Services;

use Dedoc\Scramble\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Type\Literal\LiteralStringType;
use Dedoc\Scramble\Support\Type\TypeHelper;
use Dedoc\Scramble\Support\Type\UnknownType;

class ConstFetchTypeGetter
{
public function __invoke(Scope $scope, string $className, string $constName)
{
if ($constName === 'class') {
return new LiteralStringType($className);
}

try {
$constantReflection = new \ReflectionClassConstant($className, $constName);
$constantValue = $constantReflection->getValue();

$type = TypeHelper::createTypeFromValue($constantValue);

if ($type) {
return $type;
}
} catch (\ReflectionException $e) {
return new UnknownType('Cannot get const value');
}

return new UnknownType('ConstFetchTypeGetter is not yet implemented fully for non-class const fetches.');
}
}
Loading