p2 project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
10 KiB

4 months ago
<?php
declare(strict_types=1);
/**
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tests\PHPStan;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterval;
use Carbon\PHPStan\MacroExtension;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\Type\ClosureTypeFactory;
use PHPStan\Type\VerbosityLevel;
use PHPUnit\Framework\Attributes\RequiresPhp;
use PHPUnit\Framework\Attributes\RequiresPhpunit;
#[RequiresPhpunit('<11')]
#[RequiresPhp('<8.4')]
class MacroExtensionTest extends PHPStanTestCase
{
/** @var ReflectionProvider */
private $reflectionProvider;
/** @var MacroExtension */
private $extension;
protected function setUp(): void
{
$this->reflectionProvider = $this->createReflectionProvider();
$this->extension = new MacroExtension(
$this->reflectionProvider,
self::getContainer()->getByType(ClosureTypeFactory::class)
);
}
public function testHasMacro()
{
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$this->assertFalse($this->extension->hasMethod($carbon, 'foo'));
Carbon::macro('foo', function ($someArg) {
});
$carbonInterval = $this->reflectionProvider->getClass(CarbonInterval::class);
$this->assertTrue($this->extension->hasMethod($carbon, 'foo'));
$this->assertFalse($this->extension->hasMethod($carbonInterval, 'foo'));
$this->assertFalse($this->extension->hasMethod($carbonInterval, 'foo'));
}
public function testGetMacro()
{
Carbon::macro('foo', function (): CarbonInterval {
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'foo');
$variant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$this->assertSame(
CarbonInterval::class,
$variant->getReturnType()->describe(VerbosityLevel::typeOnly()),
);
}
public function testIsStatic()
{
Carbon::macro('calendarBerlin', static function (): string {
return self::this()->tz('Europe/Berlin')->calendar();
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'calendarBerlin');
$this->assertTrue($method->isStatic());
Carbon::macro('calendarBerlinNonStatic', function (): string {
return $this->tz('Europe/Berlin')->calendar();
});
$method = $this->extension->getMethod($carbon, 'calendarBerlinNonStatic');
$this->assertFalse($method->isStatic());
}
public function testGetDeclaringClass()
{
Carbon::macro('lower', 'strtolower');
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'lower');
$this->assertSame(Carbon::class, $method->getDeclaringClass()->getName());
CarbonImmutable::macro('lowerImmutable', 'strtolower');
$carbonImmutable = $this->reflectionProvider->getClass(CarbonImmutable::class);
$method = $this->extension->getMethod($carbonImmutable, 'lowerImmutable');
$this->assertSame(CarbonImmutable::class, $method->getDeclaringClass()->getName());
}
public function testIsPrivate()
{
Carbon::macro('lowerVisibility', 'strtolower');
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'lowerVisibility');
$this->assertFalse($method->isPrivate());
}
public function testIsPublic()
{
Carbon::macro('lowerVisibility', 'strtolower');
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'lowerVisibility');
$this->assertTrue($method->isPublic());
}
public function testIsFinal()
{
$mixinClass = new class() {
// Declaring final won't apply for macro, sub-class will always be able to override macros.
final public static function foo(): string
{
return 'foo';
}
public static function bar(): string
{
return 'bar';
}
};
$carbon = $this->reflectionProvider->getClass(Carbon::class);
Carbon::macro('foo', [$mixinClass, 'foo']);
$method = $this->extension->getMethod($carbon, 'foo');
$this->assertTrue($method->isFinal()->yes());
Carbon::macro('bar', [$mixinClass, 'bar']);
$method = $this->extension->getMethod($carbon, 'bar');
$this->assertTrue($method->isFinal()->no());
}
public function testIsInternal()
{
Carbon::macro('lowerVisibility', 'strtolower');
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'lowerVisibility');
$this->assertFalse($method->isInternal()->yes());
}
public function testGetDocComment()
{
Carbon::macro(
'closureWithDocComment',
/**
* Foo.
*/
function () {
return 'foo';
},
);
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'closureWithDocComment');
$this->assertSame(
"/**\n* Foo.\n*/",
preg_replace('/^[\t ]+/m', '', $method->getDocComment()),
);
}
public function testGetName()
{
Carbon::macro('lowerVisibility', 'strtolower');
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'lowerVisibility');
$this->assertSame('lowerVisibility', $method->getName());
}
public function testGetParameters()
{
Carbon::macro('noParameters', function () {
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'noParameters');
$variant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$this->assertSame([], $variant->getParameters());
Carbon::macro('twoParameters', function (string $a, $b = 9) {
});
$method = $this->extension->getMethod($carbon, 'twoParameters');
$variant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$parameters = $variant->getParameters();
$this->assertCount(2, $parameters);
$this->assertSame('a', $parameters[0]->getName());
$this->assertNull($parameters[0]->getDefaultValue());
$this->assertSame('string', $parameters[0]->getType()->describe(VerbosityLevel::typeOnly()));
$this->assertSame('b', $parameters[1]->getName());
$this->assertNotNull($parameters[1]->getDefaultValue());
$this->assertSame('9', $parameters[1]->getDefaultValue()->describe(VerbosityLevel::value()));
$this->assertSame('mixed', $parameters[1]->getType()->describe(VerbosityLevel::typeOnly()));
}
public function testGetReturnType()
{
Carbon::macro('noReturnType', function () {
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'noReturnType');
$variant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$this->assertSame('mixed', $variant->getReturnType()->describe(VerbosityLevel::typeOnly()));
Carbon::macro('carbonReturnType', function (): Carbon {
});
$method = $this->extension->getMethod($carbon, 'carbonReturnType');
$variant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$this->assertSame(Carbon::class, $variant->getReturnType()->describe(VerbosityLevel::typeOnly()));
}
public function testIsDeprecated()
{
Carbon::macro(
'deprecated',
/**
* @deprecated since 3.0.0
*/
function () {
},
);
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'deprecated');
$this->assertTrue($method->isDeprecated()->yes());
$this->assertNull($method->getDeprecatedDescription());
Carbon::macro(
'discouraged',
/**
* @discouraged since 3.0.0
*/
function () {
},
);
$method = $this->extension->getMethod($carbon, 'discouraged');
$this->assertFalse($method->isDeprecated()->yes());
$this->assertNull($method->getDeprecatedDescription());
}
public function testIsVariadic()
{
Carbon::macro('variadic', function (...$params) {
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'variadic');
$variant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$this->assertTrue($variant->isVariadic());
Carbon::macro('notVariadic', function ($params) {
});
$method = $this->extension->getMethod($carbon, 'notVariadic');
$variant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$this->assertFalse($variant->isVariadic());
}
public function testGetPrototype()
{
Carbon::macro('prototype', function () {
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'prototype');
$this->assertSame($method, $method->getPrototype());
}
public function testGetThrowType()
{
Carbon::macro('throwType', function () {
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'throwType');
$this->assertNull($method->getThrowType());
}
public function testHasSideEffects()
{
Carbon::macro('hasSideEffects', function () {
});
$carbon = $this->reflectionProvider->getClass(Carbon::class);
$method = $this->extension->getMethod($carbon, 'hasSideEffects');
$this->assertTrue($method->hasSideEffects()->maybe());
}
}