Skip to content

Commit d2ca6d4

Browse files
authored
Merge pull request #503 from goaop/bug/functional-weaving-complex-type
[PHP8] Add broken tests with complex types
2 parents 5d7e1ad + e20f450 commit d2ca6d4

13 files changed

+861
-35
lines changed

Diff for: src/Instrument/Transformer/SelfValueVisitor.php

+24-6
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@
1919
use PhpParser\Node\Expr\New_;
2020
use PhpParser\Node\Expr\StaticCall;
2121
use PhpParser\Node\Identifier;
22+
use PhpParser\Node\IntersectionType;
2223
use PhpParser\Node\Name;
2324
use PhpParser\Node\Name\FullyQualified;
2425
use PhpParser\Node\NullableType;
2526
use PhpParser\Node\Param;
2627
use PhpParser\Node\Stmt\Catch_;
27-
use PhpParser\Node\Stmt\Class_;
28+
use PhpParser\Node\Stmt\ClassLike;
2829
use PhpParser\Node\Stmt\ClassMethod;
2930
use PhpParser\Node\Stmt\Namespace_;
3031
use PhpParser\Node\Stmt\Property;
32+
use PhpParser\Node\Stmt\Trait_;
33+
use PhpParser\Node\UnionType;
3134
use PhpParser\NodeVisitorAbstract;
3235
use UnexpectedValueException;
3336

@@ -82,10 +85,6 @@ public function enterNode(Node $node)
8285
{
8386
if ($node instanceof Namespace_) {
8487
$this->namespace = !empty($node->name) ? $node->name->toString() : null;
85-
} elseif ($node instanceof Class_) {
86-
if ($node->name !== null) {
87-
$this->className = new Name($node->name->toString());
88-
}
8988
} elseif ($node instanceof ClassMethod || $node instanceof Closure) {
9089
if (isset($node->returnType)) {
9190
$node->returnType = $this->resolveType($node->returnType);
@@ -107,6 +106,12 @@ public function enterNode(Node $node)
107106
foreach ($node->types as &$type) {
108107
$type = $this->resolveClassName($type);
109108
}
109+
} elseif ($node instanceof ClassLike) {
110+
if (! $node instanceof Trait_) {
111+
$this->className = !empty($node->name) ? new Name($node->name->toString()) : null;
112+
} else {
113+
$this->className = null;
114+
}
110115
}
111116

112117
return null;
@@ -126,6 +131,10 @@ protected function resolveClassName(Name $name): Name
126131
return $name;
127132
}
128133

134+
if ($this->className === null) {
135+
return $name;
136+
}
137+
129138
// Save the original name
130139
$originalName = $name;
131140
$name = clone $originalName;
@@ -142,7 +151,7 @@ protected function resolveClassName(Name $name): Name
142151
/**
143152
* Helper method for resolving type nodes
144153
*
145-
* @return NullableType|Name|FullyQualified|Identifier
154+
* @return NullableType|Name|FullyQualified|Identifier|UnionType|IntersectionType
146155
*/
147156
private function resolveType(Node $node)
148157
{
@@ -157,6 +166,15 @@ private function resolveType(Node $node)
157166
return $node;
158167
}
159168

169+
if ($node instanceof UnionType || $node instanceof IntersectionType) {
170+
$types = [];
171+
foreach ($node->types as $type) {
172+
$types[] = $this->resolveType($type);
173+
}
174+
$node->types = $types;
175+
return $node;
176+
}
177+
160178
throw new UnexpectedValueException('Unknown node type: ' . get_class($node));
161179
}
162180
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Go\Tests\TestProject\Application;
5+
6+
use Closure;
7+
use Countable;
8+
use Exception;
9+
use Iterator;
10+
11+
class ClassWithComplexTypes
12+
{
13+
public function publicMethodWithUnionTypeReturn(Exception|Closure $value): Exception|Closure
14+
{
15+
return $value;
16+
}
17+
18+
public function publicMethodWithIntersectionTypeReturn(Exception&Countable $value): Exception&Countable
19+
{
20+
return $value;
21+
}
22+
23+
public function publicMethodWithDNFTypeReturn(Iterator|(Exception&Countable) $value): Iterator|(Exception&Countable)
24+
{
25+
return $value;
26+
}
27+
}

Diff for: tests/Fixtures/project/src/Aspect/WeavingAspect.php

+6
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@ public function afterPublicMethodInTheInterface()
2424
{
2525
echo 'It does not intercept methods in the interface';
2626
}
27+
28+
#[Pointcut\After("within(Go\Tests\TestProject\Application\ClassWithComplexTypes) && execution(public **->*(*))")]
29+
public function afterComplexTypeMethods(): void
30+
{
31+
echo 'It intercepts methods with complex types';
32+
}
2733
}

Diff for: tests/Go/Functional/ClassWeavingTest.php

+16-5
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
namespace Go\Functional;
1414

1515
use Go\Tests\TestProject\Application\AbstractBar;
16+
use Go\Tests\TestProject\Application\ClassWithComplexTypes;
1617
use Go\Tests\TestProject\Application\FinalClass;
1718
use Go\Tests\TestProject\Application\FooInterface;
1819
use Go\Tests\TestProject\Application\Main;
1920

2021
class ClassWeavingTest extends BaseFunctionalTestCase
2122
{
22-
public function testPropertyWeaving()
23+
public function testPropertyWeaving(): void
2324
{
2425
// it weaves Main class public and protected properties
2526
$this->assertPropertyWoven(Main::class, 'publicClassProperty', 'Go\\Tests\\TestProject\\Aspect\\PropertyInterceptAspect->interceptClassProperty');
@@ -32,7 +33,7 @@ public function testPropertyWeaving()
3233
/**
3334
* test for https://github.com/goaop/framework/issues/335
3435
*/
35-
public function testItDoesNotWeaveAbstractMethods()
36+
public function testItDoesNotWeaveAbstractMethods(): void
3637
{
3738
// it weaves Main class
3839
$this->assertClassIsWoven(Main::class);
@@ -46,13 +47,13 @@ public function testItDoesNotWeaveAbstractMethods()
4647
$this->assertClassIsNotWoven(AbstractBar::class);
4748
}
4849

49-
public function testClassInitializationWeaving()
50+
public function testClassInitializationWeaving(): void
5051
{
5152
$this->assertClassInitializationWoven(Main::class, 'Go\\Tests\\TestProject\\Aspect\\InitializationAspect->beforeInstanceInitialization');
5253
$this->assertClassStaticInitializationWoven(Main::class, 'Go\\Tests\\TestProject\\Aspect\\InitializationAspect->afterClassStaticInitialization');
5354
}
5455

55-
public function testItWeavesFinalClasses()
56+
public function testItWeavesFinalClasses(): void
5657
{
5758
// it weaves FinalClass class
5859
$this->assertClassIsWoven(FinalClass::class);
@@ -70,8 +71,18 @@ public function testItWeavesFinalClasses()
7071
$this->assertMethodNotWoven(FinalClass::class, 'someFinalParentMethod');
7172
}
7273

73-
public function testItDoesNotWeaveInterfaces()
74+
public function testItDoesNotWeaveInterfaces(): void
7475
{
7576
$this->assertClassIsNotWoven(FooInterface::class);
7677
}
78+
79+
public function testItDoesWeaveMethodWithComplexTypes(): void
80+
{
81+
// it weaves ClassWithComplexTypes class
82+
$this->assertClassIsWoven(ClassWithComplexTypes::class);
83+
84+
$this->assertMethodWoven(ClassWithComplexTypes::class, 'publicMethodWithUnionTypeReturn');
85+
$this->assertMethodWoven(ClassWithComplexTypes::class, 'publicMethodWithIntersectionTypeReturn');
86+
$this->assertMethodWoven(ClassWithComplexTypes::class, 'publicMethodWithDNFTypeReturn');
87+
}
7788
}

Diff for: tests/Go/Instrument/Transformer/SelfValueTransformerTest.php

+46-24
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
use Go\Core\AspectContainer;
1616
use Go\Core\AspectKernel;
17+
use PHPUnit\Framework\Attributes\DataProvider;
1718
use PHPUnit\Framework\MockObject\MockObject;
1819
use PHPUnit\Framework\TestCase;
1920

@@ -43,15 +44,7 @@ public function setUp(): void
4344
*/
4445
protected function getKernelMock(array $options): AspectKernel
4546
{
46-
$mock = $this->getMockForAbstractClass(
47-
AspectKernel::class,
48-
[],
49-
'',
50-
false,
51-
true,
52-
true,
53-
['getOptions', 'getContainer']
54-
);
47+
$mock = $this->createMock(AspectKernel::class);
5548
$mock
5649
->method('getOptions')
5750
->willReturn($options);
@@ -63,23 +56,52 @@ protected function getKernelMock(array $options): AspectKernel
6356
return $mock;
6457
}
6558

66-
public function testTransformerReplacesAllSelfPlaces(): void
67-
{
68-
$testFile = fopen(__DIR__ . '/_files/file-with-self.php', 'rb');
69-
$content = stream_get_contents($testFile);
70-
$metadata = new StreamMetaData($testFile, $content);
71-
$this->transformer->transform($metadata);
72-
$expected = file_get_contents(__DIR__ . '/_files/file-with-self-transformed.php');
73-
$this->assertSame($expected, (string) $metadata->source);
59+
#[DataProvider("filesDataProvider")]
60+
public function testTransformerProcessFiles(
61+
string $sourceFileWithContent,
62+
string $fileWithExpectedContent,
63+
): void {
64+
try {
65+
$sourceFile = fopen($sourceFileWithContent, 'rb');
66+
$sourceContent = stream_get_contents($sourceFile);
67+
$sourceMetadata = new StreamMetaData($sourceFile, $sourceContent);
68+
$this->transformer->transform($sourceMetadata);
69+
70+
$expected = file_get_contents($fileWithExpectedContent);
71+
$this->assertSame($expected, $sourceMetadata->source);
72+
73+
} finally {
74+
if (isset($sourceFile) && is_resource($sourceFile)) {
75+
fclose($sourceFile);
76+
}
77+
}
7478
}
7579

76-
public function testTransformerReplacesAllSelfPlacesWithoutNamespace(): void
80+
public static function filesDataProvider(): \Generator
7781
{
78-
$testFile = fopen(__DIR__ . '/_files/file-with-self-no-namespace.php', 'rb');
79-
$content = stream_get_contents($testFile);
80-
$metadata = new StreamMetaData($testFile, $content);
81-
$this->transformer->transform($metadata);
82-
$expected = file_get_contents(__DIR__ . '/_files/file-with-self-no-namespace-transformed.php');
83-
$this->assertSame($expected, (string) $metadata->source);
82+
yield 'file-with-self.php' => [
83+
__DIR__ . '/_files/file-with-self.php',
84+
__DIR__ . '/_files/file-with-self-transformed.php'
85+
];
86+
yield 'file-with-self-no-namespace.php' => [
87+
__DIR__ . '/_files/file-with-self-no-namespace.php',
88+
__DIR__ . '/_files/file-with-self-no-namespace-transformed.php'
89+
];
90+
yield 'php80-file.php' => [
91+
__DIR__ . '/_files/php80-file.php',
92+
__DIR__ . '/_files/php80-file-transformed.php'
93+
];
94+
yield 'php81-file.php' => [
95+
__DIR__ . '/_files/php81-file.php',
96+
__DIR__ . '/_files/php81-file-transformed.php'
97+
];
98+
yield 'php82-file.php' => [
99+
__DIR__ . '/_files/php82-file.php',
100+
__DIR__ . '/_files/php82-file-transformed.php'
101+
];
102+
yield 'anonymous-class.php' => [
103+
__DIR__ . '/_files/anonymous-class.php',
104+
__DIR__ . '/_files/anonymous-class-transformed.php'
105+
];
84106
}
85107
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Parser Reflection API
4+
*
5+
* @copyright Copyright 2016, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*/
10+
declare(strict_types=1);
11+
12+
namespace Go\ParserReflection\Stub;
13+
14+
class InAnonymousClass
15+
{
16+
public function respond()
17+
{
18+
new class {
19+
public const FOO = 'foo';
20+
21+
public function run()
22+
{
23+
return self::FOO;
24+
}
25+
};
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Parser Reflection API
4+
*
5+
* @copyright Copyright 2016, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*/
10+
declare(strict_types=1);
11+
12+
namespace Go\ParserReflection\Stub;
13+
14+
class InAnonymousClass
15+
{
16+
public function respond()
17+
{
18+
new class {
19+
public const FOO = 'foo';
20+
21+
public function run()
22+
{
23+
return self::FOO;
24+
}
25+
};
26+
}
27+
}

0 commit comments

Comments
 (0)