Skip to content
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"rector/rector-src": "dev-main",
"rector/swiss-knife": "^1.0",
"rector/type-perfect": "^2.1",
"symplify/phpstan-rules": "^14.9.3",
"symplify/phpstan-extensions": "^12.0",
"symplify/vendor-patches": "^11.5",
"tomasvotruba/class-leak": "^1.2",
Expand Down
16 changes: 15 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
includes:
- vendor/symplify/phpstan-rules/config/symplify-rules.neon
- vendor/symplify/phpstan-rules/config/rector-rules.neon

parameters:
level: 8

Expand Down Expand Up @@ -40,4 +44,14 @@ parameters:
- '#Doing instanceof PHPStan\\Type\\.+ is error\-prone and deprecated#'

- identifier: instanceof.alwaysTrue
- identifier: assign.propertyType

# false positive
-
identifier: assign.propertyType
message: '#Property PhpParser\\Node\\Identifier\:\:\$name \(non\-empty\-string\) does not accept string#'
path: rules/CodeQuality/Rector/ClassMethod/ReplaceTestFunctionPrefixWithAttributeRector.php

# handle next
-
identifier: symplify.forbiddenNode
message: '#switch#'
5 changes: 2 additions & 3 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@
'*/Fixture/*',
'*/Expected/*',

// object types
// object types must be string as class might be missing + to keep downgrade safe
StringClassNameToClassConstantRector::class => [
__DIR__ . '/src/NodeAnalyzer/TestsNodeAnalyzer.php',
__DIR__ . '/config',
__DIR__ . '/src/NodeFinder/DataProviderClassMethodFinder.php',
__DIR__ . '/src/Enum',
],
])
->withPhpSets()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\FuncCall\AssertFuncCallToPHPUnitAssertRector\Fixture;

use PHPUnit\Framework\TestCase;

final class AssertInsideStaticClosure extends TestCase
{
public function some($response)
{
static function () use ($response) {
assert((bool) $response);
};
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\FuncCall\AssertFuncCallToPHPUnitAssertRector\Fixture;

use PHPUnit\Framework\TestCase;

final class AssertInsideStaticClosure extends TestCase
{
public function some($response)
{
static function () use ($response) {
\PHPUnit\Framework\Assert::assertTrue((bool) $response);
};
}
}

?>

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\FuncCall\AssertFuncCallToPHPUnitAssertRector\Fixture;

final class SkipOutsideTestCase
{
public function some($response)
{
assert((bool) $response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\FuncCall\AssertFuncCallToPHPUnitAssertRector\Fixture;

use PHPUnit\Framework\TestCase;

final class StaticAssertInStaticMethodTest extends TestCase
{
private static function getExceptionMessage(string $fqcn): string
{
$exception = new $fqcn();
assert($exception instanceof \Exception);

return $exception->getMessage();
}
}

?>
-----
<?php

namespace Rector\PHPUnit\Tests\CodeQuality\Rector\FuncCall\AssertFuncCallToPHPUnitAssertRector\Fixture;

use PHPUnit\Framework\TestCase;

final class StaticAssertInStaticMethodTest extends TestCase
{
private static function getExceptionMessage(string $fqcn): string
{
$exception = new $fqcn();
\PHPUnit\Framework\Assert::assertInstanceOf(\Exception::class, $exception);

return $exception->getMessage();
}
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PhpParser\Node\AttributeGroup;
use Rector\PhpAttribute\NodeFactory\PhpAttributeGroupFactory;
use Rector\PHPUnit\Enum\PHPUnitAttribute;

final readonly class RequiresAttributeFactory
{
Expand All @@ -23,7 +24,7 @@ public function create(string $annotationValue): ?AttributeGroup

switch ($type) {
case 'PHP':
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresPhp';
$attributeClass = PHPUnitAttribute::REQUIRES_PHP;

// only version is used, we need to prefix with >=
if (is_string($attributeValue) && is_numeric($attributeValue[0])) {
Expand All @@ -33,7 +34,7 @@ public function create(string $annotationValue): ?AttributeGroup
$attributeValue = [$attributeValue];
break;
case 'PHPUnit':
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresPhpunit';
$attributeClass = PHPUnitAttribute::REQUIRES_PHPUNIT;

// only version is used, we need to prefix with >=
if (is_string($attributeValue) && is_numeric($attributeValue[0])) {
Expand All @@ -43,30 +44,30 @@ public function create(string $annotationValue): ?AttributeGroup
$attributeValue = [$attributeValue];
break;
case 'OS':
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresOperatingSystem';
$attributeClass = PHPUnitAttribute::REQUIRES_OS;
$attributeValue = [$attributeValue];
break;
case 'OSFAMILY':
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily';
$attributeClass = PHPUnitAttribute::REQUIRES_OS_FAMILY;
$attributeValue = [$attributeValue];
break;
case 'function':
if (str_contains((string) $attributeValue, '::')) {
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresMethod';
$attributeClass = PHPUnitAttribute::REQUIRES_METHOD;
$attributeValue = explode('::', (string) $attributeValue);
$attributeValue[0] .= '::class';
} else {
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresFunction';
$attributeClass = PHPUnitAttribute::REQUIRES_FUNCTION;
$attributeValue = [$attributeValue];
}

break;
case 'extension':
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresPhpExtension';
$attributeClass = PHPUnitAttribute::REQUIRES_PHP_EXTENSION;
$attributeValue = explode(' ', (string) $attributeValue, 2);
break;
case 'setting':
$attributeClass = 'PHPUnit\Framework\Attributes\RequiresSetting';
$attributeClass = PHPUnitAttribute::REQUIRES_SETTING;
$attributeValue = explode(' ', (string) $attributeValue, 2);
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class AnnotationWithValueToAttributeRector extends AbstractRector implemen
*/
private array $annotationWithValueToAttributes = [];

private ?Class_ $currentClass = null;
private bool $hasChanged = false;

public function __construct(
private readonly PhpDocTagRemover $phpDocTagRemover,
Expand Down Expand Up @@ -86,7 +86,7 @@ final class SomeTest extends TestCase
*/
public function getNodeTypes(): array
{
return [Class_::class, ClassMethod::class];
return [Class_::class];
}

public function provideMinPhpVersion(): int
Expand All @@ -95,24 +95,61 @@ public function provideMinPhpVersion(): int
}

/**
* @param Class_|ClassMethod $node
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{

if (! $this->testsNodeAnalyzer->isInTestClass($node)) {
return null;
}

if ($node instanceof Class_) {
$this->currentClass = $node;
$this->hasChanged = false;

// handle class level
$this->refactorClassMethodOrClass($node, $node);

// handle method level
foreach ($node->getMethods() as $classMethod) {
$this->refactorClassMethodOrClass($classMethod, $node);
}

$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
if (! $phpDocInfo instanceof PhpDocInfo) {
if (! $this->hasChanged) {
return null;
}

$hasChanged = false;
return $node;
}

/**
* @param mixed[] $configuration
*/
public function configure(array $configuration): void
{
Assert::allIsInstanceOf($configuration, AnnotationWithValueToAttribute::class);
$this->annotationWithValueToAttributes = $configuration;
}

private function resolveAttributeValue(
GenericTagValueNode $genericTagValueNode,
AnnotationWithValueToAttribute $annotationWithValueToAttribute
): mixed {
$valueMap = $annotationWithValueToAttribute->getValueMap();
if ($valueMap === []) {
// no map? convert value as it is
return $genericTagValueNode->value;
}

$originalValue = strtolower($genericTagValueNode->value);
return $valueMap[$originalValue];
}

private function refactorClassMethodOrClass(ClassMethod|Class_ $classOrClass, Class_ $class): void
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($classOrClass);
if (! $phpDocInfo instanceof PhpDocInfo) {
return;
}

foreach ($this->annotationWithValueToAttributes as $annotationWithValueToAttribute) {
/** @var PhpDocTagNode[] $desiredTagValueNodes */
Expand All @@ -133,47 +170,18 @@ public function refactor(Node $node): ?Node
[$attributeValue]
);

if ($node instanceof ClassMethod && $annotationWithValueToAttribute->getIsOnClassLevel() && $this->currentClass instanceof Class_) {
Assert::isAOf($this->currentClass, Class_::class);
$this->currentClass->attrGroups = array_merge($this->currentClass->attrGroups, [$attributeGroup]);
if ($annotationWithValueToAttribute->getIsOnClassLevel()) {
$class->attrGroups = array_merge($class->attrGroups, [$attributeGroup]);
} else {
$node->attrGroups = array_merge($node->attrGroups, [$attributeGroup]);
$classOrClass->attrGroups = array_merge($classOrClass->attrGroups, [$attributeGroup]);
}

// cleanup
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $desiredTagValueNode);
$hasChanged = true;
}
}

if ($hasChanged) {
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
return $node;
}

return null;
}
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classOrClass);

/**
* @param mixed[] $configuration
*/
public function configure(array $configuration): void
{
Assert::allIsInstanceOf($configuration, AnnotationWithValueToAttribute::class);
$this->annotationWithValueToAttributes = $configuration;
}

private function resolveAttributeValue(
GenericTagValueNode $genericTagValueNode,
AnnotationWithValueToAttribute $annotationWithValueToAttribute
): mixed {
$valueMap = $annotationWithValueToAttribute->getValueMap();
if ($valueMap === []) {
// no map? convert value as it is
return $genericTagValueNode->value;
$this->hasChanged = true;
}
}

$originalValue = strtolower($genericTagValueNode->value);
return $valueMap[$originalValue];
}
}
Loading