Skip to content

[PHP8] Add broken tests with complex types #503

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

Merged
merged 3 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 24 additions & 6 deletions src/Instrument/Transformer/SelfValueVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\Catch_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\UnionType;
use PhpParser\NodeVisitorAbstract;
use UnexpectedValueException;

Expand Down Expand Up @@ -82,10 +85,6 @@ public function enterNode(Node $node)
{
if ($node instanceof Namespace_) {
$this->namespace = !empty($node->name) ? $node->name->toString() : null;
} elseif ($node instanceof Class_) {
if ($node->name !== null) {
$this->className = new Name($node->name->toString());
}
} elseif ($node instanceof ClassMethod || $node instanceof Closure) {
if (isset($node->returnType)) {
$node->returnType = $this->resolveType($node->returnType);
Expand All @@ -107,6 +106,12 @@ public function enterNode(Node $node)
foreach ($node->types as &$type) {
$type = $this->resolveClassName($type);
}
} elseif ($node instanceof ClassLike) {
if (! $node instanceof Trait_) {
$this->className = !empty($node->name) ? new Name($node->name->toString()) : null;
} else {
$this->className = null;
}
}

return null;
Expand All @@ -126,6 +131,10 @@ protected function resolveClassName(Name $name): Name
return $name;
}

if ($this->className === null) {
return $name;
}

// Save the original name
$originalName = $name;
$name = clone $originalName;
Expand All @@ -142,7 +151,7 @@ protected function resolveClassName(Name $name): Name
/**
* Helper method for resolving type nodes
*
* @return NullableType|Name|FullyQualified|Identifier
* @return NullableType|Name|FullyQualified|Identifier|UnionType|IntersectionType
*/
private function resolveType(Node $node)
{
Expand All @@ -157,6 +166,15 @@ private function resolveType(Node $node)
return $node;
}

if ($node instanceof UnionType || $node instanceof IntersectionType) {
$types = [];
foreach ($node->types as $type) {
$types[] = $this->resolveType($type);
}
$node->types = $types;
return $node;
}

throw new UnexpectedValueException('Unknown node type: ' . get_class($node));
}
}
27 changes: 27 additions & 0 deletions tests/Fixtures/project/src/Application/ClassWithComplexTypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
declare(strict_types = 1);

namespace Go\Tests\TestProject\Application;

use Closure;
use Countable;
use Exception;
use Iterator;

class ClassWithComplexTypes
{
public function publicMethodWithUnionTypeReturn(Exception|Closure $value): Exception|Closure
{
return $value;
}

public function publicMethodWithIntersectionTypeReturn(Exception&Countable $value): Exception&Countable
{
return $value;
}

public function publicMethodWithDNFTypeReturn(Iterator|(Exception&Countable) $value): Iterator|(Exception&Countable)
{
return $value;
}
}
6 changes: 6 additions & 0 deletions tests/Fixtures/project/src/Aspect/WeavingAspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,10 @@ public function afterPublicMethodInTheInterface()
{
echo 'It does not intercept methods in the interface';
}

#[Pointcut\After("within(Go\Tests\TestProject\Application\ClassWithComplexTypes) && execution(public **->*(*))")]
public function afterComplexTypeMethods(): void
{
echo 'It intercepts methods with complex types';
}
}
21 changes: 16 additions & 5 deletions tests/Go/Functional/ClassWeavingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
namespace Go\Functional;

use Go\Tests\TestProject\Application\AbstractBar;
use Go\Tests\TestProject\Application\ClassWithComplexTypes;
use Go\Tests\TestProject\Application\FinalClass;
use Go\Tests\TestProject\Application\FooInterface;
use Go\Tests\TestProject\Application\Main;

class ClassWeavingTest extends BaseFunctionalTestCase
{
public function testPropertyWeaving()
public function testPropertyWeaving(): void
{
// it weaves Main class public and protected properties
$this->assertPropertyWoven(Main::class, 'publicClassProperty', 'Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty');
Expand All @@ -32,7 +33,7 @@ public function testPropertyWeaving()
/**
* test for https://github.com/goaop/framework/issues/335
*/
public function testItDoesNotWeaveAbstractMethods()
public function testItDoesNotWeaveAbstractMethods(): void
{
// it weaves Main class
$this->assertClassIsWoven(Main::class);
Expand All @@ -46,13 +47,13 @@ public function testItDoesNotWeaveAbstractMethods()
$this->assertClassIsNotWoven(AbstractBar::class);
}

public function testClassInitializationWeaving()
public function testClassInitializationWeaving(): void
{
$this->assertClassInitializationWoven(Main::class, 'Go\\Tests\\TestProject\\Aspect\\InitializationAspect->beforeInstanceInitialization');
$this->assertClassStaticInitializationWoven(Main::class, 'Go\\Tests\\TestProject\\Aspect\\InitializationAspect->afterClassStaticInitialization');
}

public function testItWeavesFinalClasses()
public function testItWeavesFinalClasses(): void
{
// it weaves FinalClass class
$this->assertClassIsWoven(FinalClass::class);
Expand All @@ -70,8 +71,18 @@ public function testItWeavesFinalClasses()
$this->assertMethodNotWoven(FinalClass::class, 'someFinalParentMethod');
}

public function testItDoesNotWeaveInterfaces()
public function testItDoesNotWeaveInterfaces(): void
{
$this->assertClassIsNotWoven(FooInterface::class);
}

public function testItDoesWeaveMethodWithComplexTypes(): void
{
// it weaves ClassWithComplexTypes class
$this->assertClassIsWoven(ClassWithComplexTypes::class);

$this->assertMethodWoven(ClassWithComplexTypes::class, 'publicMethodWithUnionTypeReturn');
$this->assertMethodWoven(ClassWithComplexTypes::class, 'publicMethodWithIntersectionTypeReturn');
$this->assertMethodWoven(ClassWithComplexTypes::class, 'publicMethodWithDNFTypeReturn');
}
}
70 changes: 46 additions & 24 deletions tests/Go/Instrument/Transformer/SelfValueTransformerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use Go\Core\AspectContainer;
use Go\Core\AspectKernel;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -43,15 +44,7 @@ public function setUp(): void
*/
protected function getKernelMock(array $options): AspectKernel
{
$mock = $this->getMockForAbstractClass(
AspectKernel::class,
[],
'',
false,
true,
true,
['getOptions', 'getContainer']
);
$mock = $this->createMock(AspectKernel::class);
$mock
->method('getOptions')
->willReturn($options);
Expand All @@ -63,23 +56,52 @@ protected function getKernelMock(array $options): AspectKernel
return $mock;
}

public function testTransformerReplacesAllSelfPlaces(): void
{
$testFile = fopen(__DIR__ . '/_files/file-with-self.php', 'rb');
$content = stream_get_contents($testFile);
$metadata = new StreamMetaData($testFile, $content);
$this->transformer->transform($metadata);
$expected = file_get_contents(__DIR__ . '/_files/file-with-self-transformed.php');
$this->assertSame($expected, (string) $metadata->source);
#[DataProvider("filesDataProvider")]
public function testTransformerProcessFiles(
string $sourceFileWithContent,
string $fileWithExpectedContent,
): void {
try {
$sourceFile = fopen($sourceFileWithContent, 'rb');
$sourceContent = stream_get_contents($sourceFile);
$sourceMetadata = new StreamMetaData($sourceFile, $sourceContent);
$this->transformer->transform($sourceMetadata);

$expected = file_get_contents($fileWithExpectedContent);
$this->assertSame($expected, $sourceMetadata->source);

} finally {
if (isset($sourceFile) && is_resource($sourceFile)) {
fclose($sourceFile);
}
}
}

public function testTransformerReplacesAllSelfPlacesWithoutNamespace(): void
public static function filesDataProvider(): \Generator
{
$testFile = fopen(__DIR__ . '/_files/file-with-self-no-namespace.php', 'rb');
$content = stream_get_contents($testFile);
$metadata = new StreamMetaData($testFile, $content);
$this->transformer->transform($metadata);
$expected = file_get_contents(__DIR__ . '/_files/file-with-self-no-namespace-transformed.php');
$this->assertSame($expected, (string) $metadata->source);
yield 'file-with-self.php' => [
__DIR__ . '/_files/file-with-self.php',
__DIR__ . '/_files/file-with-self-transformed.php'
];
yield 'file-with-self-no-namespace.php' => [
__DIR__ . '/_files/file-with-self-no-namespace.php',
__DIR__ . '/_files/file-with-self-no-namespace-transformed.php'
];
yield 'php80-file.php' => [
__DIR__ . '/_files/php80-file.php',
__DIR__ . '/_files/php80-file-transformed.php'
];
yield 'php81-file.php' => [
__DIR__ . '/_files/php81-file.php',
__DIR__ . '/_files/php81-file-transformed.php'
];
yield 'php82-file.php' => [
__DIR__ . '/_files/php82-file.php',
__DIR__ . '/_files/php82-file-transformed.php'
];
yield 'anonymous-class.php' => [
__DIR__ . '/_files/anonymous-class.php',
__DIR__ . '/_files/anonymous-class-transformed.php'
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Parser Reflection API
*
* @copyright Copyright 2016, Lisachenko Alexander <[email protected]>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);

namespace Go\ParserReflection\Stub;

class InAnonymousClass
{
public function respond()
{
new class {
public const FOO = 'foo';

public function run()
{
return self::FOO;
}
};
}
}
27 changes: 27 additions & 0 deletions tests/Go/Instrument/Transformer/_files/anonymous-class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Parser Reflection API
*
* @copyright Copyright 2016, Lisachenko Alexander <[email protected]>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);

namespace Go\ParserReflection\Stub;

class InAnonymousClass
{
public function respond()
{
new class {
public const FOO = 'foo';

public function run()
{
return self::FOO;
}
};
}
}
Loading
Loading