Skip to content

Commit

Permalink
Merge pull request #1 from dedoc/feature/smart-scope
Browse files Browse the repository at this point in the history
Feature/smart scope
  • Loading branch information
romalytvynenko authored Sep 1, 2022
2 parents e3ade14 + 684d266 commit 6168cbe
Show file tree
Hide file tree
Showing 35 changed files with 768 additions and 121 deletions.
1 change: 1 addition & 0 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ function (string $name) use ($routeInfo) {
);
}
} catch (\Throwable $exception) {
throw $exception;
$description = $description->append('⚠️Cannot generate request documentation: '.$exception->getMessage());
}

Expand Down
7 changes: 6 additions & 1 deletion src/Support/ClassAstHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Dedoc\Scramble\Support;

use Dedoc\Scramble\Support\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Infer\TypeInferringVisitor;
use Illuminate\Support\Arr;
use PhpParser\Node;
Expand All @@ -20,6 +21,8 @@ class ClassAstHelper

public Node\Stmt\Class_ $classAst;

public Scope $scope;

public $namesResolver = null;

private ?PhpDocNode $phpDoc = null;
Expand All @@ -45,9 +48,11 @@ private function init()
);

$traverser = new NodeTraverser;
$traverser->addVisitor(new TypeInferringVisitor($this->namesResolver));
$traverser->addVisitor($infer = new TypeInferringVisitor($this->namesResolver));
$traverser->traverse([$classAst]);

$this->scope = $infer->scope;

if (! $classAst) {
throw new \InvalidArgumentException('Cannot create class AST of the class '.$this->class);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Support/ComplexTypeHandler/JsonResourceHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function handle()
return null;
}

if (! $type = $returnNode->expr->getAttribute('type')) {
if (! $type = $classAstHelper->scope->getType($returnNode->expr)) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Support/Generator/Types/OpenApiTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class OpenApiTypeHelper
{
public static function fromType(\Dedoc\Scramble\Support\Type\AbstractType $type): Type
public static function fromType(\Dedoc\Scramble\Support\Type\Type $type): Type
{
$openApiType = new StringType();

Expand Down
9 changes: 4 additions & 5 deletions src/Support/Infer/Handler/ArrayHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Dedoc\Scramble\Support\Infer\Handler;

use Dedoc\Scramble\Support\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Type\ArrayType;
use PhpParser\Node;

Expand All @@ -12,15 +13,13 @@ public function shouldHandle($node)
return $node instanceof Node\Expr\Array_;
}

public function leave(Node\Expr\Array_ $node)
public function leave(Node\Expr\Array_ $node, Scope $scope)
{
$arrayItems = collect($node->items)
->filter()
->map(function (Node\Expr\ArrayItem $arrayItem) {
return $arrayItem->getAttribute('type');
})
->map(fn (Node\Expr\ArrayItem $arrayItem) => $scope->getType($arrayItem))
->all();

$node->setAttribute('type', new ArrayType($arrayItems));
$scope->setType($node, new ArrayType($arrayItems));
}
}
10 changes: 5 additions & 5 deletions src/Support/Infer/Handler/ArrayItemHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Dedoc\Scramble\Support\Infer\Handler;

use Dedoc\Scramble\Support\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Type\ArrayItemType_;
use Dedoc\Scramble\Support\Type\UnknownType;
use PhpParser\Node;

class ArrayItemHandler
Expand All @@ -13,13 +13,13 @@ public function shouldHandle($node)
return $node instanceof Node\Expr\ArrayItem;
}

public function leave(Node\Expr\ArrayItem $node)
public function leave(Node\Expr\ArrayItem $node, Scope $scope)
{
$node->setAttribute(
'type',
$scope->setType(
$node,
new ArrayItemType_(
$node->key->value ?? null,
$node->value->getAttribute('type', new UnknownType()),
$scope->getType($node->value),
$isOptional = false,
)
);
Expand Down
12 changes: 4 additions & 8 deletions src/Support/Infer/Handler/ClassHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,18 @@ class ClassHandler implements CreatesScope
{
public function createScope(Scope $scope): Scope
{
return new Scope(
clone $scope->context,
$scope->namesResolver,
$scope
);
return $scope->createChildScope(clone $scope->context);
}

public function shouldHandle($node)
{
return $node instanceof Node\Stmt\Class_;
}

public function enter(Node\Stmt\Class_ $node)
public function enter(Node\Stmt\Class_ $node, Scope $scope)
{
$node->getAttribute('scope')->context->setClass(
new ObjectType($node->name ? $node->getAttribute('scope')->resolveName($node->name->toString()) : 'anonymous@class'),
$scope->context->setClass(
new ObjectType($node->name ? $scope->resolveName($node->name->toString()) : 'anonymous@class'),
);
}
}
63 changes: 31 additions & 32 deletions src/Support/Infer/Handler/FunctionLikeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,64 @@
namespace Dedoc\Scramble\Support\Infer\Handler;

use Dedoc\Scramble\Support\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Type\FunctionLikeType;
use Dedoc\Scramble\Support\Type\FunctionType;
use Dedoc\Scramble\Support\Type\TypeHelper;
use Dedoc\Scramble\Support\Type\Union;
use Dedoc\Scramble\Support\Type\VoidType;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\NodeFinder;

class FunctionLikeHandler implements CreatesScope
{
public function createScope(Scope $scope): Scope
{
return new Scope(
clone $scope->context,
$scope->namesResolver,
$scope
);
return $scope->createChildScope(clone $scope->context);
}

public function shouldHandle($node)
{
return $node instanceof FunctionLike;
}

public function enter(FunctionLike $node)
public function enter(FunctionLike $node, Scope $scope)
{
$type = new FunctionType();
// when entering function node, the only thing we need/want to do
// is to set node param types to scope.
// Also, if here we add a reference to the function node type, it may allow us to
// set function return types not in leave function, but in the return handlers.
$scope->setType($node, $fnType = new FunctionType);

$node->setAttribute('type', $type);
$scope->context->setFunction($fnType);
}

public function leave(FunctionLike $node)
public function leave(FunctionLike $node, Scope $scope)
{
/** @var $type FunctionLikeType */
if (! $type = $node->getAttribute('type')) {
throw new \LogicException('Type should have been set on node, but was not.');
}
$type = $scope->context->function;

if ($returnTypeAnnotation = $node->getReturnType()) {
$type->setReturnType(TypeHelper::createTypeFromTypeNode($returnTypeAnnotation) ?: new VoidType);

return;
// @todo Here we may not need to go deep in the fn and analyze nodes as we already know the type.
} else {
// Simple way of handling the arrow functions, as they do not have a return statement.
// So here we just create a "virtual" return and processing it as by default.
if ($node instanceof Node\Expr\ArrowFunction) {
(new ReturnHandler)->leave(
new Node\Stmt\Return_($node->expr, $node->getAttributes()),
$scope,
);
}
}

/** @var Node\Stmt\Return_[] $returnNodes */
$returnNodes = (new NodeFinder)->find(
$node->getStmts(),
function (Node $n) use ($node) {
return $n instanceof Node\Stmt\Return_
&& $node->getAttribute('scope') === ($n->getAttribute('scope') ?: $n->expr->getAttribute('scope'));
// In case of method in class being analyzed, we want to attach the method information
// to the class so classes can be analyzed later.
if ($node instanceof Node\Stmt\ClassMethod) {
// @todo: remove as this should not happen - class must be always there
if (! $scope->context->class) {
return;
}
);

$types = array_filter(array_map(
fn (Node\Stmt\Return_ $n) => $n->expr ? $n->expr->getAttribute('type') : new VoidType,
$returnNodes,
));

$type->setReturnType(Union::wrap($types));
$scope->context->class->methods = array_merge(
$scope->context->class->methods,
[$node->name->name => $type],
);
}
}
}
7 changes: 4 additions & 3 deletions src/Support/Infer/Handler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Dedoc\Scramble\Support\Infer\Handler;

use Dedoc\Scramble\Support\Infer\Scope\Scope;
use Dedoc\Scramble\Support\Type\ObjectType;
use PhpParser\Node;

Expand All @@ -12,14 +13,14 @@ public function shouldHandle($node)
return $node instanceof Node\Expr\New_;
}

public function leave(Node\Expr\New_ $node)
public function leave(Node\Expr\New_ $node, Scope $scope)
{
if (! ($node->class instanceof Node\Name)) {
return null;
}

$node->setAttribute(
'type',
$scope->setType(
$node,
new ObjectType($node->class->toString()),
);
}
Expand Down
12 changes: 6 additions & 6 deletions src/Support/Infer/Handler/PropertyFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Dedoc\Scramble\Support\Infer\Handler;

use Dedoc\Scramble\Support\Infer\Scope\Scope;
use PhpParser\Node;

class PropertyFetchHandler
Expand All @@ -11,16 +12,15 @@ public function shouldHandle($node)
return $node instanceof Node\Expr\PropertyFetch;
}

public function leave(Node\Expr\PropertyFetch $node)
public function leave(Node\Expr\PropertyFetch $node, Scope $scope)
{
if (! $type = $node->var->getAttribute('type')) {
return null;
}

// Only string property names support.
if (! $name = ($node->name->name ?? null)) {
return null;
}

$node->setAttribute('type', $type->getPropertyFetchType($name));
$type = $scope->getType($node->var);

$scope->setType($node, $type->getPropertyFetchType($name));
}
}
33 changes: 33 additions & 0 deletions src/Support/Infer/Handler/ReturnHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Dedoc\Scramble\Support\Infer\Handler;

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

class ReturnHandler
{
public function shouldHandle($node)
{
return $node instanceof Node\Stmt\Return_;
}

public function leave(Node\Stmt\Return_ $node, Scope $scope)
{
$fnType = $scope->context->function;

if (! ($fnType instanceof FunctionType)) {
return;
}

$fnType->setReturnType(
TypeHelper::mergeTypes(
$node->expr ? $scope->getType($node->expr) : new VoidType,
$fnType->getReturnType(),
)
);
}
}
11 changes: 6 additions & 5 deletions src/Support/Infer/Handler/ReturnTypeGettingExtensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

namespace Dedoc\Scramble\Support\Infer\Handler;

use Dedoc\Scramble\Support\Infer\Scope\Scope;
use PhpParser\Node;

class ReturnTypeGettingExtensions
{
public static $extensions = [];

public function shouldHandle($node)
public function shouldHandle()
{
return true;
}

public function leave(Node $node)
public function leave(Node $node, Scope $scope)
{
$type = array_reduce(
static::$extensions,
function ($acc, $extensionClass) use ($node) {
$type = (new $extensionClass)->getNodeReturnType($node, $node->getAttribute('scope'));
function ($acc, $extensionClass) use ($node, $scope) {
$type = (new $extensionClass)->getNodeReturnType($node, $scope);
if ($type) {
return $type;
}
Expand All @@ -28,7 +29,7 @@ function ($acc, $extensionClass) use ($node) {
);

if ($type) {
$node->setAttribute('type', $type);
$scope->setType($node, $type);
}
}
}
30 changes: 0 additions & 30 deletions src/Support/Infer/Handler/ScalarHandler.php

This file was deleted.

Loading

0 comments on commit 6168cbe

Please sign in to comment.