Skip to content

Commit

Permalink
[TypeDeclarations] Add IncreaseDeclareStrictTypesRector to raise leve…
Browse files Browse the repository at this point in the history
…l of declare() coverage (#5849)

* [TypeDeclarations] Add IncreaseDeclareStrictTypesRector to raise level of declare() coverage

* [ci-review] Rector Rectify

---------

Co-authored-by: GitHub Action <actions@github.com>
  • Loading branch information
TomasVotruba and actions-user committed Apr 30, 2024
1 parent ce1033d commit 57275ef
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector\Fixture;

function anotherFunction()
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector\Fixture;

function someFunction()
{
}

?>
-----
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector\Fixture;

function someFunction()
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\StmtsAwareInterface\IncreaseDeclareStrictTypesRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class IncreaseDeclareStrictTypesRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\IncreaseDeclareStrictTypesRector;

return RectorConfig::configure()
->withRules([IncreaseDeclareStrictTypesRector::class]);
28 changes: 28 additions & 0 deletions rules/TypeDeclaration/NodeAnalyzer/DeclareStrictTypeFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclaration\NodeAnalyzer;

use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Declare_;

final readonly class DeclareStrictTypeFinder
{
public function hasDeclareStrictTypes(Stmt $stmt): bool
{
// when first stmt is Declare_, verify if there is strict_types definition already,
// as multiple declare is allowed, with declare(strict_types=1) only allowed on very first stmt
if (! $stmt instanceof Declare_) {
return false;
}

foreach ($stmt->declares as $declare) {
if ($declare->key->toString() === 'strict_types') {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Rector\Contract\PhpParser\Node\StmtsAwareInterface;
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Rector\AbstractRector;
use Rector\TypeDeclaration\NodeAnalyzer\DeclareStrictTypeFinder;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

Expand All @@ -23,6 +24,11 @@
*/
final class DeclareStrictTypesRector extends AbstractRector
{
public function __construct(
private readonly DeclareStrictTypeFinder $declareStrictTypeFinder
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add declare(strict_types=1) if missing', [
Expand Down Expand Up @@ -78,11 +84,9 @@ public function beforeTraverse(array $nodes): ?array
$stmt = $currentStmt;
}

if (! $stmt instanceof Stmt) {
return null;
}

if ($this->shouldSkip($stmt)) {
// when first stmt is Declare_, verify if there is strict_types definition already,
// as multiple declare is allowed, with declare(strict_types=1) only allowed on very first stmt
if ($this->declareStrictTypeFinder->hasDeclareStrictTypes($stmt)) {
return null;
}

Expand Down Expand Up @@ -117,19 +121,4 @@ public function refactor(Node $node): ?Node
// workaroudn, as Rector now only hooks to specific nodes, not arrays
return null;
}

private function shouldSkip(Stmt $stmt): bool
{
// when first stmt is Declare_, verify if there is strict_types definition already,
// as multiple declare is allowed, with declare(strict_types=1) only allowed on very first stmt
if ($stmt instanceof Declare_) {
foreach ($stmt->declares as $declare) {
if ($declare->key->toString() === 'strict_types') {
return true;
}
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclaration\Rector\StmtsAwareInterface;

use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Declare_;
use PhpParser\Node\Stmt\DeclareDeclare;
use PhpParser\Node\Stmt\Nop;
use Rector\ChangesReporting\ValueObject\RectorWithLineChange;
use Rector\Contract\PhpParser\Node\StmtsAwareInterface;
use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Rector\AbstractRector;
use Rector\TypeDeclaration\NodeAnalyzer\DeclareStrictTypeFinder;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Webmozart\Assert\Assert;

/**
* @see \Rector\Tests\TypeDeclaration\Rector\StmtsAwareInterface\IncreaseDeclareStrictTypesRector\IncreaseDeclareStrictTypesRectorTest
*/
final class IncreaseDeclareStrictTypesRector extends AbstractRector implements ConfigurableRectorInterface
{
private const LIMIT = 'limit';

private int $limit = 10;

private int $changedItemCount = 0;

public function __construct(
private readonly DeclareStrictTypeFinder $declareStrictTypeFinder
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Add declare strict types to a limited amount of classes at a time, to try out in the wild and increase level gradually',
[
new ConfiguredCodeSample(
<<<'CODE_SAMPLE'
function someFunction()
{
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
declare(strict_types=1);
function someFunction()
{
}
CODE_SAMPLE
,
[
self::LIMIT => 10,
],
),
]
);
}

/**
* @param Node[] $nodes
* @return Node[]|null
*/
public function beforeTraverse(array $nodes): ?array
{
parent::beforeTraverse($nodes);

$newStmts = $this->file->getNewStmts();
if ($newStmts === []) {
return null;
}

$rootStmt = \current($newStmts);
$stmt = $rootStmt;

// skip classes without namespace for safety reasons
if ($rootStmt instanceof FileWithoutNamespace) {
return null;
}

if ($this->declareStrictTypeFinder->hasDeclareStrictTypes($stmt)) {
return null;
}

// keep change withing a limit
if ($this->changedItemCount >= $this->limit) {
return null;
}

++$this->changedItemCount;

$strictTypesDeclare = $this->creteStrictTypesDeclare();

$rectorWithLineChange = new RectorWithLineChange(self::class, $stmt->getLine());
$this->file->addRectorClassWithLine($rectorWithLineChange);

return \array_merge([$strictTypesDeclare, new Nop()], $nodes);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [StmtsAwareInterface::class];
}

/**
* @param StmtsAwareInterface $node
*/
public function refactor(Node $node): ?Node
{
// workaround, as Rector now only hooks to specific nodes, not arrays
return null;
}

public function configure(array $configuration): void
{
Assert::keyExists($configuration, self::LIMIT);
$this->limit = (int) $configuration[self::LIMIT];
}

private function creteStrictTypesDeclare(): Declare_
{
$declareDeclare = new DeclareDeclare(new Identifier('strict_types'), new LNumber(1));
return new Declare_([$declareDeclare]);
}
}
2 changes: 2 additions & 0 deletions utils/Command/MissingInSetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Rector\Privatization\Rector\Class_\FinalizeTestCaseClassRector;
use Rector\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\IncreaseDeclareStrictTypesRector;
use Rector\TypeDeclaration\Rector\While_\WhileNullableToInstanceofRector;
use Rector\Utils\Enum\RectorDirectoryToSetFileMap;
use Rector\Utils\Finder\RectorClassFinder;
Expand All @@ -45,6 +46,7 @@ final class MissingInSetCommand extends Command
StrvalToTypeCastRector::class,
BoolvalToTypeCastRector::class,
FloatvalToTypeCastRector::class,
IncreaseDeclareStrictTypesRector::class,
// changes behavior, should be applied on purpose regardless PHP 7.3 level
JsonThrowOnErrorRector::class,
// in confront with sub type safe belt detection on RemoveUseless*TagRector
Expand Down

0 comments on commit 57275ef

Please sign in to comment.