mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-01 00:40:52 +00:00
Merge pull request #28 from TomasVotruba/nette-forms-set-required
[Nette] forms setRequired(false)
This commit is contained in:
commit
645d92dba9
|
@ -3,7 +3,7 @@
|
|||
[![Build Status](https://img.shields.io/travis/TomasVotruba/Rector/master.svg?style=flat-square)](https://travis-ci.org/TomasVotruba/Rector)
|
||||
[![Coverage Status](https://img.shields.io/coveralls/TomasVotruba/Rector/master.svg?style=flat-square)](https://coveralls.io/github/TomasVotruba/Rector?branch=master)
|
||||
|
||||
This tool will *reconstruct* (change) your code - **run it only in a new clean git branch**.
|
||||
This tool will **upgrade your application** for you.
|
||||
|
||||
|
||||
## All Reconstructors
|
||||
|
@ -15,14 +15,12 @@ This tool will *reconstruct* (change) your code - **run it only in a new clean g
|
|||
- `HtmlAddMethodRector`
|
||||
- `NetteObjectToSmartTraitRector`
|
||||
- `RemoveConfiguratorConstantsRector`
|
||||
- and many others
|
||||
|
||||
### [Symfony](https://github.com/symfony/)
|
||||
|
||||
- `NamedServicesToConstructorNodeTraverser`
|
||||
|
||||
### Abstract to use
|
||||
|
||||
- `AbstractChangeMethodNameRector`
|
||||
- and many others
|
||||
|
||||
|
||||
## Install
|
||||
|
|
|
@ -24,13 +24,15 @@
|
|||
"autoload": {
|
||||
"psr-4": {
|
||||
"Rector\\": "src",
|
||||
"Rector\\NodeTypeResolver\\": "packages/NodeTypeResolver/src"
|
||||
"Rector\\NodeTypeResolver\\": "packages/NodeTypeResolver/src",
|
||||
"Rector\\TriggerExtractor\\": "packages/TriggerExtractor/src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Rector\\Tests\\": "tests",
|
||||
"Rector\\NodeTypeResolver\\Tests\\": "packages/NodeTypeResolver/tests"
|
||||
"Rector\\NodeTypeResolver\\Tests\\": "packages/NodeTypeResolver/tests",
|
||||
"Rector\\TriggerExtractor\\Tests\\": "packages/TriggerExtractor/tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
|
52
packages/NodeTypeResolver/README.md
Normal file
52
packages/NodeTypeResolver/README.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
# Node Type Resolver
|
||||
|
||||
This package detects `class`, type `class_node` and `$variable` or `$this->property` types and adds them to all relevant nodes.
|
||||
|
||||
Class type is added to all nodes inside it, so you always now where you are.
|
||||
|
||||
Anonymous classes are skipped.
|
||||
|
||||
|
||||
## How it works?
|
||||
|
||||
1. Traverse all nodes
|
||||
2. Detect variable assigns, property use, method arguments
|
||||
3. Resolve types
|
||||
4. Add them via `$node->setAttribute('type', $type);` to ever node
|
||||
|
||||
|
||||
## How it helps you?
|
||||
|
||||
You can get `class`
|
||||
|
||||
```php
|
||||
$class = (string) $node->getAttribute('class');
|
||||
|
||||
if (Strings::endsWith($class, 'Command')) {
|
||||
// we are in Command class
|
||||
}
|
||||
|
||||
// to be sure it's console command
|
||||
|
||||
/** @var PhpParser\Node\Name\FullyQualified $fqnName */
|
||||
$classNode = $node->getAttribute('class_node');
|
||||
|
||||
$fqnName = $classNode->extends->getAttribute('resolvedName');
|
||||
|
||||
if ($fqnName->toString() === 'Symfony\Component\Console\Command') {
|
||||
// we are sure it's child of Symfony\Console Command class
|
||||
}
|
||||
```
|
||||
|
||||
or `type` attribute:
|
||||
|
||||
```php
|
||||
/** @var string $type */
|
||||
$type = $node->var->getAttribute('type');
|
||||
|
||||
if ($type === 'Nette\Application\UI\Form') {
|
||||
// this is Nette\Application\UI\Form variable
|
||||
}
|
||||
```
|
||||
|
||||
...in any Rector you create.
|
49
packages/NodeTypeResolver/src/NodeVisitor/ClassResolver.php
Normal file
49
packages/NodeTypeResolver/src/NodeVisitor/ClassResolver.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeTypeResolver\NodeVisitor;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
/**
|
||||
* Add attribute 'class' with current class name.
|
||||
*/
|
||||
final class ClassResolver extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const CLASS_ATTRIBUTE = 'class';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const CLASS_NODE_ATTRIBUTE = 'class_node';
|
||||
|
||||
/**
|
||||
* @var Class_|null
|
||||
*/
|
||||
private $classNode;
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
*/
|
||||
public function beforeTraverse(array $nodes): void
|
||||
{
|
||||
$this->classNode = null;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node): void
|
||||
{
|
||||
// detect only first "class" to prevent anonymous classes interference
|
||||
if ($this->classNode === null && $node instanceof Class_) {
|
||||
$this->classNode = $node;
|
||||
}
|
||||
|
||||
if ($this->classNode) {
|
||||
$node->setAttribute(self::CLASS_NODE_ATTRIBUTE, $this->classNode);
|
||||
$node->setAttribute(self::CLASS_ATTRIBUTE, $this->classNode->namespacedName->toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ use Rector\NodeTypeResolver\TypeContext;
|
|||
/**
|
||||
* Inspired by https://github.com/nikic/PHP-Parser/blob/9373a8e9f551516bc8e42aedeacd1b4f635d27fc/lib/PhpParser/NodeVisitor/NameResolver.php.
|
||||
*/
|
||||
final class ClassLikeTypeResolver extends NodeVisitorAbstract
|
||||
final class TypeResolver extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var string
|
25
packages/TriggerExtractor/README.md
Normal file
25
packages/TriggerExtractor/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Trigger Extractor
|
||||
|
||||
This package extracts `trigger_error(*, E_USER_DEPRECATED)` from the code.
|
||||
|
||||
It helps to generated automate rectors.
|
||||
|
||||
|
||||
## How it works?
|
||||
|
||||
1. Just read the message
|
||||
2. Detect what code should be change into what code
|
||||
3. Create rectors that will do the work
|
||||
|
||||
|
||||
## How it helps you?
|
||||
|
||||
Instead of using your eyes and brain to read `CHANGELOG.md` or `UPGRADE-x.md`, just run it over directory of framework you use:
|
||||
|
||||
```bash
|
||||
vendor/bin/rector extract-deprecations vendor/nette
|
||||
```
|
||||
|
||||
It will show you what changed and how.
|
||||
|
||||
Moreover, it will change the code for you.
|
|
@ -0,0 +1,55 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TriggerExtractor\Console\Command;
|
||||
|
||||
use Rector\TriggerExtractor\TriggerExtractor;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
final class ExtractCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const NAME = 'extract-deprecations';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const ARGUMENT_SOURCE_NAME = 'source';
|
||||
|
||||
/**
|
||||
* @var TriggerExtractor
|
||||
*/
|
||||
private $triggerExtractor;
|
||||
|
||||
public function __construct(TriggerExtractor $triggerExtractor)
|
||||
{
|
||||
$this->triggerExtractor = $triggerExtractor;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName(self::NAME);
|
||||
$this->setDescription('Extract deprecation notes from PHP files in directory(ies).');
|
||||
$this->addArgument(
|
||||
self::ARGUMENT_SOURCE_NAME,
|
||||
InputArgument::REQUIRED | InputArgument::IS_ARRAY,
|
||||
'One or more directory to be checked.'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$source = $input->getArgument(self::ARGUMENT_SOURCE_NAME);
|
||||
$this->triggerExtractor->scanDirectories($source);
|
||||
|
||||
// write found deprecations...
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TriggerExtractor\Deprecation;
|
||||
|
||||
final class DeprecationCollector
|
||||
{
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $deprecations = [];
|
||||
|
||||
public function addDeprecation(string $deprecation): void
|
||||
{
|
||||
$this->deprecations[] = $deprecation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getDeprecations(): array
|
||||
{
|
||||
return $this->deprecations;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TriggerExtractor\NodeVisitor;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use Rector\TriggerExtractor\Deprecation\DeprecationCollector;
|
||||
use Rector\TriggerExtractor\TriggerMessageResolver;
|
||||
|
||||
final class DeprecationDetector extends NodeVisitorAbstract
|
||||
{
|
||||
/**
|
||||
* @var DeprecationCollector
|
||||
*/
|
||||
private $deprecationCollector;
|
||||
|
||||
/**
|
||||
* @var TriggerMessageResolver
|
||||
*/
|
||||
private $triggerMessageResolver;
|
||||
|
||||
public function __construct(
|
||||
DeprecationCollector $deprecationCollector,
|
||||
TriggerMessageResolver $triggerMessageResolver
|
||||
) {
|
||||
$this->deprecationCollector = $deprecationCollector;
|
||||
$this->triggerMessageResolver = $triggerMessageResolver;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node): void
|
||||
{
|
||||
if (! $this->isTriggerErrorUserDeprecated($node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var FuncCall $node */
|
||||
$deprecation = $this->triggerMessageResolver->resolve($node->args[0]->value);
|
||||
|
||||
$this->deprecationCollector->addDeprecation($deprecation);
|
||||
}
|
||||
|
||||
/**
|
||||
* This detects: "trigger_error(<some-content>, E_USER_DEPREDCATED)";
|
||||
*/
|
||||
private function isTriggerErrorUserDeprecated(Node $node): bool
|
||||
{
|
||||
if (! $this->isFunctionWithName($node, 'trigger_error')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var FuncCall $node */
|
||||
if (count($node->args) !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Arg $secondArgumentNode */
|
||||
$secondArgumentNode = $node->args[1];
|
||||
if (! $secondArgumentNode->value instanceof ConstFetch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var ConstFetch $constFetchNode */
|
||||
$constFetchNode = $secondArgumentNode->value;
|
||||
|
||||
return $constFetchNode->name->toString() === 'E_USER_DEPRECATED';
|
||||
}
|
||||
|
||||
private function isFunctionWithName(Node $node, string $name): bool
|
||||
{
|
||||
if (! $node instanceof FuncCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $node->name instanceof Name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $node->name->toString() === $name;
|
||||
}
|
||||
}
|
73
packages/TriggerExtractor/src/TriggerExtractor.php
Normal file
73
packages/TriggerExtractor/src/TriggerExtractor.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TriggerExtractor;
|
||||
|
||||
use Rector\Contract\Parser\ParserInterface;
|
||||
use Rector\NodeTraverser\MainNodeTraverser;
|
||||
use Rector\NodeTraverser\StandaloneTraverseNodeTraverser;
|
||||
use Rector\TriggerExtractor\NodeVisitor\DeprecationDetector;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
final class TriggerExtractor
|
||||
{
|
||||
/**
|
||||
* @var ParserInterface
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @var MainNodeTraverser
|
||||
*/
|
||||
private $mainNodeTraverser;
|
||||
|
||||
/**
|
||||
* @var StandaloneTraverseNodeTraverser
|
||||
*/
|
||||
private $standaloneTraverseNodeTraverser;
|
||||
|
||||
public function __construct(
|
||||
ParserInterface $parser,
|
||||
MainNodeTraverser $mainNodeTraverser,
|
||||
DeprecationDetector $deprecationDetector,
|
||||
StandaloneTraverseNodeTraverser $standaloneTraverseNodeTraverser
|
||||
) {
|
||||
$this->parser = $parser;
|
||||
$this->standaloneTraverseNodeTraverser = $standaloneTraverseNodeTraverser;
|
||||
|
||||
$this->mainNodeTraverser = $mainNodeTraverser;
|
||||
$this->mainNodeTraverser->addVisitor($deprecationDetector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $directories
|
||||
*/
|
||||
public function scanDirectories(array $directories): void
|
||||
{
|
||||
$files = $this->findPhpFilesInDirectories($directories);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$nodes = $this->parser->parseFile($file->getRealPath());
|
||||
// this completes parent & child nodes, types and classses
|
||||
$this->standaloneTraverseNodeTraverser->traverse($nodes);
|
||||
$this->mainNodeTraverser->traverse($nodes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo duplicated method to
|
||||
* @see \Rector\Console\Command\ReconstructCommand, extract to class
|
||||
*
|
||||
* @param string[] $directories
|
||||
* @return SplFileInfo[] array
|
||||
*/
|
||||
private function findPhpFilesInDirectories(array $directories): array
|
||||
{
|
||||
$finder = Finder::create()
|
||||
->files()
|
||||
->name('*.php')
|
||||
->in($directories);
|
||||
|
||||
return iterator_to_array($finder->getIterator());
|
||||
}
|
||||
}
|
81
packages/TriggerExtractor/src/TriggerMessageResolver.php
Normal file
81
packages/TriggerExtractor/src/TriggerMessageResolver.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TriggerExtractor;
|
||||
|
||||
use Exception;
|
||||
use Nette\Utils\Strings;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Scalar\MagicConst\Method;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
final class TriggerMessageResolver
|
||||
{
|
||||
/**
|
||||
* Probably resolve by recursion, similar too
|
||||
* @see \Rector\NodeTypeResolver\NodeVisitor\TypeResolver::__construct()
|
||||
*/
|
||||
public function resolve(Node $node): string
|
||||
{
|
||||
$message = '';
|
||||
if ($node instanceof Concat) {
|
||||
$message .= $this->processConcatNode($node->left);
|
||||
$message .= $this->processConcatNode($node->right);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private function processConcatNode(Node $node): string
|
||||
{
|
||||
if ($node instanceof Method) {
|
||||
$classMethodNode = $this->findParentOfType($node, ClassMethod::class);
|
||||
|
||||
return $node->getAttribute('class') . '::' . $classMethodNode->name->name;
|
||||
}
|
||||
|
||||
if ($node instanceof String_) {
|
||||
$message = $node->value; // complet class to local methods
|
||||
return $this->completeClassToLocalMethods($message, $node->getAttribute('class'));
|
||||
}
|
||||
|
||||
throw new Exception(sprintf(
|
||||
'Not implemented yet. Go to "%s::%s()" and add check for "%s" node.',
|
||||
__CLASS__,
|
||||
__METHOD__,
|
||||
get_class($node)
|
||||
));
|
||||
}
|
||||
|
||||
private function findParentOfType(Node $node, string $type): Node
|
||||
{
|
||||
$parentNode = $node->getAttribute('parent');
|
||||
|
||||
while (! is_a($parentNode, $type, true)) {
|
||||
$parentNode = $parentNode->getAttribute('parent');
|
||||
}
|
||||
|
||||
return $parentNode;
|
||||
}
|
||||
|
||||
private function completeClassToLocalMethods(string $message, string $class): string
|
||||
{
|
||||
$completeMessage = '';
|
||||
$words = explode(' ', $message);
|
||||
|
||||
foreach ($words as $word) {
|
||||
// is method()
|
||||
if (Strings::endsWith($word, '()') && strlen($word) > 2) {
|
||||
// doesn't include class in the beggning
|
||||
if (! Strings::startsWith($word, $class)) {
|
||||
$word = $class . '::' . $word;
|
||||
}
|
||||
}
|
||||
|
||||
$completeMessage .= ' ' . $word;
|
||||
}
|
||||
|
||||
return trim($completeMessage);
|
||||
}
|
||||
}
|
7
packages/TriggerExtractor/src/config/services.yml
Normal file
7
packages/TriggerExtractor/src/config/services.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
|
||||
# PSR-4 autodiscovery
|
||||
Rector\TriggerExtractor\:
|
||||
resource: '../../src'
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
final class Definition
|
||||
{
|
||||
/**
|
||||
* @return static
|
||||
* @deprecated
|
||||
*/
|
||||
public function setClass(?string $type): self
|
||||
{
|
||||
if (func_num_args() > 1) {
|
||||
@trigger_error(__METHOD__ . '() second parameter $args is deprecated, use setFactory()', E_USER_DEPRECATED);
|
||||
if ($args = func_get_arg(1)) {
|
||||
$this->setFactory($type, $args);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setInject(bool $state = true): self
|
||||
{
|
||||
@trigger_error(__METHOD__ . "() is deprecated, use addTag('inject')", E_USER_DEPRECATED);
|
||||
|
||||
return $this->addTag(InjectExtension::TAG_INJECT, $state);
|
||||
}
|
||||
}
|
42
packages/TriggerExtractor/tests/TriggerExtractorTest.php
Normal file
42
packages/TriggerExtractor/tests/TriggerExtractorTest.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\TriggerExtractor\Tests;
|
||||
|
||||
use Rector\Tests\AbstractContainerAwareTestCase;
|
||||
use Rector\TriggerExtractor\Deprecation\DeprecationCollector;
|
||||
use Rector\TriggerExtractor\TriggerExtractor;
|
||||
|
||||
final class TriggerExtractorTest extends AbstractContainerAwareTestCase
|
||||
{
|
||||
/**
|
||||
* @var TriggerExtractor
|
||||
*/
|
||||
private $triggerExtractor;
|
||||
|
||||
/**
|
||||
* @var DeprecationCollector
|
||||
*/
|
||||
private $deprecationCollector;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->triggerExtractor = $this->container->get(TriggerExtractor::class);
|
||||
$this->deprecationCollector = $this->container->get(DeprecationCollector::class);
|
||||
}
|
||||
|
||||
public function test(): void
|
||||
{
|
||||
$this->triggerExtractor->scanDirectories([__DIR__ . '/TriggerExtractorSource']);
|
||||
$deprecations = $this->deprecationCollector->getDeprecations();
|
||||
|
||||
$this->assertCount(2, $deprecations);
|
||||
|
||||
$setClassToSetFacoryDeprecation = $deprecations[0];
|
||||
|
||||
$this->assertSame(
|
||||
'Nette\DI\Definition::setClass() second parameter $args is deprecated,'
|
||||
. ' use Nette\DI\Definition::setFactory()',
|
||||
$setClassToSetFacoryDeprecation
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,4 +3,6 @@ parameters:
|
|||
- '#Internal error#'
|
||||
- '#Undefined variable: \$possibleAutoloadPaths#'
|
||||
- '#Undefined variable: \$container#'
|
||||
- '#Undefined variable: \$application#'
|
||||
- '#Undefined variable: \$application#'
|
||||
excludes_analyse:
|
||||
- *packages/TriggerExtractor/tests/TriggerExtractorSource/Definition.php
|
||||
|
|
|
@ -15,7 +15,7 @@ final class ReconstructCommand extends Command
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const NAME = 'processClass';
|
||||
private const NAME = 'process';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
|
52
src/NodeAnalyzer/ClassResolver.php
Normal file
52
src/NodeAnalyzer/ClassResolver.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
|
||||
final class ClassResolver
|
||||
{
|
||||
/**
|
||||
* @var Class_|null
|
||||
*/
|
||||
private $classNode;
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
*/
|
||||
public function resolveFromNodes(array $nodes): void
|
||||
{
|
||||
$this->classNode = null;
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if ($node instanceof Class_) {
|
||||
$this->classNode = $node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
if ($this->classNode === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->classNode->namespacedName->toString();
|
||||
}
|
||||
|
||||
public function getParentClassName(): string
|
||||
{
|
||||
if ($this->classNode === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parentClass = $this->classNode->extends;
|
||||
|
||||
/** @var Node\Name\FullyQualified $fqnParentClassName */
|
||||
$fqnParentClassName = $parentClass->getAttribute('resolvedName');
|
||||
|
||||
return $fqnParentClassName->toString();
|
||||
}
|
||||
}
|
|
@ -3,10 +3,23 @@
|
|||
namespace Rector\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
|
||||
final class MethodCallAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param string[] $methodsNames
|
||||
*/
|
||||
public function isMethodCallTypeAndMethods(Node $node, string $type, array $methodsNames): bool
|
||||
{
|
||||
if (! $this->isMethodCallType($node, $type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array((string) $node->name, $methodsNames, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $methodNames
|
||||
*/
|
||||
|
@ -28,6 +41,20 @@ final class MethodCallAnalyzer
|
|||
return false;
|
||||
}
|
||||
|
||||
private function isMethodCallType(Node $node, string $type): bool
|
||||
{
|
||||
if (! $node instanceof MethodCall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$variableType = $this->findVariableType($node);
|
||||
if ($variableType !== $type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isStaticMethodCallType(Node $node, string $type): bool
|
||||
{
|
||||
if (! $node instanceof StaticCall) {
|
||||
|
@ -40,4 +67,15 @@ final class MethodCallAnalyzer
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function findVariableType(MethodCall $methodCallNode): string
|
||||
{
|
||||
$varNode = $methodCallNode->var;
|
||||
|
||||
while ($varNode->getAttribute('type') === null) {
|
||||
$varNode = $varNode->var;
|
||||
}
|
||||
|
||||
return (string) $varNode->getAttribute('type');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Rector\NodeFactory;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
|
@ -35,6 +36,11 @@ final class NodeFactory
|
|||
return new ConstFetch(new Name('null'));
|
||||
}
|
||||
|
||||
public function createFalseConstant(): ConstFetch
|
||||
{
|
||||
return new ConstFetch(new Name('false'));
|
||||
}
|
||||
|
||||
public function createClassConstant(string $className, string $constantName): ClassConstFetch
|
||||
{
|
||||
$classNameNode = new FullyQualified($className);
|
||||
|
@ -83,4 +89,18 @@ final class NodeFactory
|
|||
'kind' => Array_::KIND_SHORT,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $arguments
|
||||
* @return Arg[]
|
||||
*/
|
||||
public function createArgs(array $arguments): array
|
||||
{
|
||||
$args = [];
|
||||
foreach ($arguments as $argument) {
|
||||
$args[] = new Arg($argument);
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,14 @@ abstract class AbstractChangeParentClassRector extends AbstractRector
|
|||
{
|
||||
public function isCandidate(Node $node): bool
|
||||
{
|
||||
if (! $node instanceof Class_) {
|
||||
if (! $node instanceof Class_ || $node->extends === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getParentClassName() === $this->getOldClassName();
|
||||
/** @var FullyQualified $fqnName */
|
||||
$fqnName = $node->extends->getAttribute('resolvedName');
|
||||
|
||||
return $fqnName->toString() === $this->getOldClassName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Rector\Rector;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use Rector\Contract\Deprecation\DeprecationInterface;
|
||||
|
@ -11,29 +10,6 @@ use Rector\Contract\Rector\RectorInterface;
|
|||
|
||||
abstract class AbstractRector extends NodeVisitorAbstract implements DeprecationInterface, RectorInterface
|
||||
{
|
||||
/**
|
||||
* @var Class_|null
|
||||
*/
|
||||
protected $classNode;
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
* @return null|Node[]
|
||||
*/
|
||||
public function beforeTraverse(array $nodes): ?array
|
||||
{
|
||||
$this->classNode = null;
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
if ($node instanceof Class_) {
|
||||
$this->classNode = $node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|int|Node
|
||||
*/
|
||||
|
@ -49,27 +25,4 @@ abstract class AbstractRector extends NodeVisitorAbstract implements Deprecation
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getClassName(): string
|
||||
{
|
||||
if ($this->classNode === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->classNode->namespacedName->toString();
|
||||
}
|
||||
|
||||
protected function getParentClassName(): string
|
||||
{
|
||||
if ($this->classNode === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parentClass = $this->classNode->extends;
|
||||
|
||||
/** @var Node\Name\FullyQualified $fqnParentClassName */
|
||||
$fqnParentClassName = $parentClass->getAttribute('resolvedName');
|
||||
|
||||
return $fqnParentClassName->toString();
|
||||
}
|
||||
}
|
||||
|
|
84
src/Rector/Contrib/Nette/FormSetRequiredRector.php
Normal file
84
src/Rector/Contrib/Nette/FormSetRequiredRector.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Rector\Contrib\Nette;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use Rector\Deprecation\SetNames;
|
||||
use Rector\NodeAnalyzer\MethodCallAnalyzer;
|
||||
use Rector\NodeFactory\NodeFactory;
|
||||
use Rector\Rector\AbstractRector;
|
||||
|
||||
/**
|
||||
* Covers https://forum.nette.org/cs/26672-missing-setrequired-true-false-on-field-abc-in-form
|
||||
*/
|
||||
final class FormSetRequiredRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const FORM_CLASS = 'Nette\Application\UI\Form';
|
||||
|
||||
/**
|
||||
* @var MethodCallAnalyzer
|
||||
*/
|
||||
private $methodCallAnalyzer;
|
||||
|
||||
/**
|
||||
* @var NodeFactory
|
||||
*/
|
||||
private $nodeFactory;
|
||||
|
||||
public function __construct(MethodCallAnalyzer $methodCallAnalyzer, NodeFactory $nodeFactory)
|
||||
{
|
||||
$this->methodCallAnalyzer = $methodCallAnalyzer;
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
}
|
||||
|
||||
public function getSetName(): string
|
||||
{
|
||||
return SetNames::NETTE;
|
||||
}
|
||||
|
||||
public function sinceVersion(): float
|
||||
{
|
||||
return 2.4;
|
||||
}
|
||||
|
||||
public function isCandidate(Node $node): bool
|
||||
{
|
||||
if (! $this->methodCallAnalyzer->isMethodCallTypeAndMethods($node, self::FORM_CLASS, ['addCondition'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var MethodCall $node */
|
||||
if (count($node->args) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$arg = $node->args[0];
|
||||
if (! $arg->value instanceof ClassConstFetch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($arg->value->class->getAttribute('type') !== self::FORM_CLASS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $arg->value->name->name === 'FILLED';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MethodCall $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$node->name->name = 'setRequired';
|
||||
$node->args = $this->nodeFactory->createArgs([
|
||||
$this->nodeFactory->createFalseConstant(),
|
||||
]);
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace Rector\Rector\Contrib\Nette;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use Rector\Builder\StatementGlue;
|
||||
use Rector\Deprecation\SetNames;
|
||||
|
@ -52,15 +53,14 @@ final class NetteObjectToSmartTraitRector extends AbstractRector
|
|||
|
||||
public function isCandidate(Node $node): bool
|
||||
{
|
||||
if (! $node instanceof Class_) {
|
||||
if (! $node instanceof Class_ || $node->extends === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $node->extends) {
|
||||
return false;
|
||||
}
|
||||
/** @var FullyQualified $fqnName */
|
||||
$fqnName = $node->extends->getAttribute('resolvedName');
|
||||
|
||||
return $this->getParentClassName() === self::PARENT_CLASS;
|
||||
return $fqnName->toString() === self::PARENT_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -87,7 +87,9 @@ final class CommandToConstructorInjectionRector extends AbstractRector
|
|||
|
||||
public function isCandidate(Node $node): bool
|
||||
{
|
||||
if (! Strings::endsWith($this->getClassName(), 'Command')) {
|
||||
$class = (string) $node->getAttribute('class');
|
||||
|
||||
if (! Strings::endsWith($class, 'Command')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -103,7 +105,7 @@ final class CommandToConstructorInjectionRector extends AbstractRector
|
|||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
$this->replaceParentContainerAwareCommandWithCommand();
|
||||
$this->replaceParentContainerAwareCommandWithCommand($node);
|
||||
|
||||
$serviceType = $this->serviceFromKernelResolver->resolveServiceClassFromArgument(
|
||||
$node->args[0],
|
||||
|
@ -116,13 +118,18 @@ final class CommandToConstructorInjectionRector extends AbstractRector
|
|||
|
||||
$propertyName = $this->nameResolver->resolvePropertyNameFromType($serviceType);
|
||||
|
||||
$this->classPropertyCollector->addPropertyForClass($this->getClassName(), $serviceType, $propertyName);
|
||||
$this->classPropertyCollector->addPropertyForClass(
|
||||
(string) $node->getAttribute('class'),
|
||||
$serviceType,
|
||||
$propertyName
|
||||
);
|
||||
|
||||
return $this->nodeFactory->createLocalPropertyFetch($propertyName);
|
||||
}
|
||||
|
||||
private function replaceParentContainerAwareCommandWithCommand(): void
|
||||
private function replaceParentContainerAwareCommandWithCommand(Node $node): void
|
||||
{
|
||||
$this->classNode->extends = new FullyQualified('Symfony\Component\Console\Command\Command');
|
||||
$classNode = $node->getAttribute('class_node');
|
||||
$classNode->extends = new FullyQualified('Symfony\Component\Console\Command\Command');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,11 @@ final class GetterToPropertyRector extends AbstractRector
|
|||
|
||||
$propertyName = $this->nameResolver->resolvePropertyNameFromType($serviceType);
|
||||
|
||||
$this->classPropertyCollector->addPropertyForClass($this->getClassName(), $serviceType, $propertyName);
|
||||
$this->classPropertyCollector->addPropertyForClass(
|
||||
(string) $methodCallNode->getAttribute('class'),
|
||||
$serviceType,
|
||||
$propertyName
|
||||
);
|
||||
|
||||
return $this->nodeFactory->createLocalPropertyFetch($propertyName);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
imports:
|
||||
- { resource: '../../packages/TriggerExtractor/src/config/services.yml' }
|
||||
|
||||
parameters:
|
||||
# todo
|
||||
kernel_class: # for name based service refactoring
|
||||
|
@ -7,7 +10,6 @@ parameters:
|
|||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
# PSR-4 autodiscovery
|
||||
Rector\:
|
||||
|
@ -21,7 +23,8 @@ services:
|
|||
calls:
|
||||
- ['addNodeVisitor', ['@PhpParser\NodeVisitor\NameResolver']]
|
||||
- ['addNodeVisitor', ['@Rector\NodeVisitor\NodeConnector']]
|
||||
- ['addNodeVisitor', ['@Rector\NodeTypeResolver\NodeVisitor\ClassLikeTypeResolver']]
|
||||
- ['addNodeVisitor', ['@Rector\NodeTypeResolver\NodeVisitor\TypeResolver']]
|
||||
- ['addNodeVisitor', ['@Rector\NodeTypeResolver\NodeVisitor\ClassResolver']]
|
||||
|
||||
# 3rd party services
|
||||
Symfony\Component\Console\Application:
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?php declare (strict_types=1);
|
||||
|
||||
class SomePresenter
|
||||
{
|
||||
public function createNetteForm()
|
||||
{
|
||||
$form = new \Nette\Application\UI\Form;
|
||||
$form->addText('name')
|
||||
->setRequired(false)
|
||||
->addRule('...')
|
||||
->addRule('...');
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
25
tests/Rector/Contrib/Nette/FormSetRequiredRector/Test.php
Normal file
25
tests/Rector/Contrib/Nette/FormSetRequiredRector/Test.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Rector\Contrib\Nette\FormSetRequiredRector;
|
||||
|
||||
use Rector\Rector\Contrib\Nette\FormSetRequiredRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class Test extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFileMatchesExpectedContent(
|
||||
__DIR__ . '/Wrong/wrong.php.inc',
|
||||
__DIR__ . '/Correct/correct.php.inc'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getRectorClasses(): array
|
||||
{
|
||||
return [FormSetRequiredRector::class];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php declare (strict_types=1);
|
||||
|
||||
class SomePresenter
|
||||
{
|
||||
public function createNetteForm()
|
||||
{
|
||||
$form = new \Nette\Application\UI\Form;
|
||||
$form->addText('name')
|
||||
->addCondition($form::FILLED)
|
||||
->addRule('...')
|
||||
->addRule('...');
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user