Skip to content

Commit

Permalink
Merge pull request #503 from goaop/bug/functional-weaving-complex-type
Browse files Browse the repository at this point in the history
[PHP8] Add broken tests with complex types
  • Loading branch information
lisachenko authored May 7, 2024
2 parents 5d7e1ad + e20f450 commit d2ca6d4
Show file tree
Hide file tree
Showing 13 changed files with 861 additions and 35 deletions.
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

0 comments on commit d2ca6d4

Please sign in to comment.