Skip to content

Commit

Permalink
wip src/Foundation/Support/UpdateHasHttpClientDocCommentRector.php
Browse files Browse the repository at this point in the history
  • Loading branch information
guanguans committed Jan 29, 2024
1 parent eaa9735 commit 64d8f9e
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 6 deletions.
4 changes: 4 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* This source file is subject to the MIT license that is bundled.
*/

use Guanguans\Notify\Foundation\Support\UpdateHasHttpClientDocCommentRector;
use Guanguans\Notify\Foundation\Support\UpdateHasOptionsDocCommentRector;
use Rector\Caching\ValueObject\Storage\FileCacheStorage;
use Rector\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFunctionRector;
Expand Down Expand Up @@ -91,6 +92,8 @@
AddSeeTestAnnotationRector::class,

// paths
__DIR__.'/src/Clients',
__DIR__.'/src/Messages',
'**/Fixture*',
'**/Fixture/*',
'**/Fixtures*',
Expand Down Expand Up @@ -132,6 +135,7 @@

$rectorConfig->rules([
// InlineConstructorDefaultToPropertyRector::class,
UpdateHasHttpClientDocCommentRector::class,
UpdateHasOptionsDocCommentRector::class,
]);
};
175 changes: 175 additions & 0 deletions src/Foundation/Support/UpdateHasHttpClientDocCommentRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

declare(strict_types=1);

/**
* This file is part of the guanguans/notify.
*
* (c) guanguans <[email protected]>
*
* This source file is subject to the MIT license that is bundled.
*/

namespace Guanguans\Notify\Foundation\Support;

use Guanguans\Notify\Foundation\Traits\HasHttpClient;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use PhpParser\Node;
use PhpParser\Node\Stmt\Trait_;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;

/**
* @see \Rector\PHPUnit\CodeQuality\Rector\Class_\AddSeeTestAnnotationRector
*/
class UpdateHasHttpClientDocCommentRector extends AbstractRector implements ConfigurableRectorInterface
{
private const MAIN_CLASS = \Guanguans\Notify\Foundation\Client::class;
private const TRAIT = HasHttpClient::class;

private array $except = [
'__construct',
'__call',
];

private array $mixins = [
HandlerStack::class,
Client::class,
];

private DocBlockUpdater $docBlockUpdater;
private PhpDocInfoFactory $phpDocInfoFactory;

public function __construct(DocBlockUpdater $docBlockUpdater, PhpDocInfoFactory $phpDocInfoFactory)
{
$this->except = array_merge($this->except, array_map(
static fn (\ReflectionMethod $reflectionMethod) => $reflectionMethod->getName(),
(new \ReflectionClass(self::MAIN_CLASS))->getMethods(\ReflectionMethod::IS_PUBLIC)
));
$this->docBlockUpdater = $docBlockUpdater;
$this->phpDocInfoFactory = $phpDocInfoFactory;
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Update has http client doc comment',
[
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
trait HasHttpClient {}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
/**
* ...
* @method self setHandler(callable $handler)
* @method self hasHandler()
* ...
*
* @mixin \Guanguans\Notify\Foundation\Client
*/
trait HasHttpClient {}
CODE_SAMPLE
,
['__*']
),
]
);
}

public function configure(array $configuration): void
{
Assert::allStringNotEmpty($configuration);
$this->except = array_merge($this->except, $configuration);
}

public function getNodeTypes(): array
{
return [Trait_::class];
}

/**
* @param Trait_ $node
*
* @throws \ReflectionException
*/
public function refactor(Node $node)
{
/** @var class-string $trait */
$trait = $node->getAttribute('scope')->getClassReflection()->getName();
if (self::TRAIT !== $trait) {
return;
}

$node->setAttribute('comments', null);

$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
foreach ($this->mixins as $mixin) {
$reflectionMethods = array_filter(
(new \ReflectionClass($mixin))->getMethods(\ReflectionMethod::IS_PUBLIC),
fn (\ReflectionMethod $reflectionMethod) => ! Str::is($this->except, $reflectionMethod->getName())
);

foreach ($reflectionMethods as $reflectionMethod) {
$phpDocInfo->addPhpDocTagNode($this->createMethodPhpDocTagNode($reflectionMethod));
}
}

$phpDocInfo->addPhpDocTagNode(new PhpDocTagNode('', new GenericTagValueNode('')));
$phpDocInfo->addPhpDocTagNode(new PhpDocTagNode('@mixin', new GenericTagValueNode('\\'.self::MAIN_CLASS)));

$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);

return $node;
}

private function createMethodPhpDocTagNode(\ReflectionMethod $reflectionMethod): PhpDocTagNode
{
$static = $reflectionMethod->isStatic() ? 'static ' : '';

$name = $reflectionMethod->getName();

$parameters = rtrim(
array_reduce(
$reflectionMethod->getParameters(),
static function (string $carry, \ReflectionParameter $reflectionParameter) {
if ($reflectionParameter->hasType()) {
/** @noinspection PhpVoidFunctionResultUsedInspection */
$type = $reflectionParameter->getType();
\assert($type instanceof \ReflectionNamedType);
$type->isBuiltin() or $carry .= '\\';
$carry .= $type->getName().' ';
}

$carry .= '$'.$reflectionParameter->getName();

if ($reflectionParameter->isDefaultValueAvailable()) {
$defaultValue = $reflectionParameter->getDefaultValue();

/** @noinspection DebugFunctionUsageInspection */
$export = var_export($defaultValue, true);
null === $defaultValue and $export = 'null';
[] === $defaultValue and $export = '[]';

$carry .= ' = '.$export;
}

return $carry.', ';
},
''
),
', '
);

return new PhpDocTagNode('@method', new GenericTagValueNode("{$static}self $name($parameters)"));
}
}
31 changes: 29 additions & 2 deletions src/Foundation/Traits/HasHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,35 @@
use GuzzleHttp\HandlerStack;

/**
* @mixin \GuzzleHttp\HandlerStack
* @mixin \GuzzleHttp\Client
* @method static self create(callable $handler = null)
* @method self __invoke(\Psr\Http\Message\RequestInterface $request, array $options)
* @method self __toString()
* @method self setHandler(callable $handler)
* @method self hasHandler()
* @method self unshift(callable $middleware, string $name = null)
* @method self push(callable $middleware, string $name = '')
* @method self before(string $findName, callable $middleware, string $withName = '')
* @method self after(string $findName, callable $middleware, string $withName = '')
* @method self remove($remove)
* @method self resolve()
* @method self sendAsync(\Psr\Http\Message\RequestInterface $request, array $options = [])
* @method self sendRequest(\Psr\Http\Message\RequestInterface $request)
* @method self requestAsync(string $method, $uri = '', array $options = [])
* @method self request(string $method, $uri = '', array $options = [])
* @method self getConfig(string $option = null)
* @method self get($uri, array $options = [])
* @method self head($uri, array $options = [])
* @method self put($uri, array $options = [])
* @method self post($uri, array $options = [])
* @method self patch($uri, array $options = [])
* @method self delete($uri, array $options = [])
* @method self getAsync($uri, array $options = [])
* @method self headAsync($uri, array $options = [])
* @method self putAsync($uri, array $options = [])
* @method self postAsync($uri, array $options = [])
* @method self patchAsync($uri, array $options = [])
* @method self deleteAsync($uri, array $options = [])
*
* @mixin \Guanguans\Notify\Foundation\Client
*/
trait HasHttpClient
Expand Down
8 changes: 6 additions & 2 deletions src/NowPush/Messages/ImageMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@

namespace Guanguans\Notify\NowPush\Messages;

use Guanguans\Notify\Messages\Message;

/**
* @method \Guanguans\Notify\NowPush\Messages\ImageMessage messageType($messageType)
* @method \Guanguans\Notify\NowPush\Messages\ImageMessage note($note)
* @method \Guanguans\Notify\NowPush\Messages\ImageMessage deviceType($deviceType)
* @method \Guanguans\Notify\NowPush\Messages\ImageMessage url($url)
*/
class ImageMessage extends Message
{
/**
Expand Down
8 changes: 6 additions & 2 deletions src/NowPush/Messages/LinkMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@

namespace Guanguans\Notify\NowPush\Messages;

use Guanguans\Notify\Messages\Message;

/**
* @method \Guanguans\Notify\NowPush\Messages\LinkMessage messageType($messageType)
* @method \Guanguans\Notify\NowPush\Messages\LinkMessage note($note)
* @method \Guanguans\Notify\NowPush\Messages\LinkMessage deviceType($deviceType)
* @method \Guanguans\Notify\NowPush\Messages\LinkMessage url($url)
*/
class LinkMessage extends Message
{
/**
Expand Down

0 comments on commit 64d8f9e

Please sign in to comment.