diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bec95c4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ad2b2d1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore + +ecs.php export-ignore +phpstan.neon export-ignore +phpunit.xml export-ignore +stubs export-ignore +tests export-ignore diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml new file mode 100644 index 0000000..87a9a1b --- /dev/null +++ b/.github/workflows/code_analysis.yaml @@ -0,0 +1,35 @@ +name: Code Analysis + +on: + pull_request: null + push: + branches: + - main + +jobs: + code_analysis: + runs-on: ubuntu-latest + strategy: + matrix: + actions: + - + name: 'PHPStan' + run: composer phpstan + - + name: 'ECS' + run: composer check-cs + + name: ${{ matrix.actions.name }} + + steps: + - uses: actions/checkout@v2 + + - + uses: shivammathur/setup-php@v2 + with: + php-version: 7.3 + coverage: none + + - uses: "ramsey/composer-install@v1" + + - run: ${{ matrix.actions.run }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..ea75389 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,29 @@ +name: Tests + +on: + pull_request: null + push: + branches: + - main + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['7.3', '7.4', '8.0'] + + name: PHP ${{ matrix.php }} tests + steps: + - uses: actions/checkout@v2 + + - + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - uses: "ramsey/composer-install@v1" + + - run: vendor/bin/phpunit tests diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4cd6c70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +/vendor +composer.lock + +# PHPStorm meta files +.idea/ + +.phpunit.result.cache + +# often customized locally - example on Github is just fine +rector-recipe.php + +# testing +abz + +# scoped & downgraded version +php-scoper.phar +box.phar +php-parallel-lint + +tmp diff --git a/README.md b/README.md new file mode 100644 index 0000000..78be909 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Rector Rules for Nette + +## Install + +```bash +composer require rector/rector-nette +``` + +@todo diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ff26644 --- /dev/null +++ b/composer.json @@ -0,0 +1,36 @@ +{ + "name": "rector/rector-nette", + "license": "MIT", + "description": "Rector upgrades rules for Nette Framework", + "require": { + "php": ">=7.3", + "ext-xml": "*", + "rector/rector": "dev-main" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symplify/phpstan-rules": "^9.2", + "symplify/phpstan-extensions": "^9.2", + "symplify/easy-coding-standard": "^9.2" + }, + "autoload": { + "psr-4": { + "Rector\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Rector\\Tests\\": "tests" + }, + "classmap": [ + "stubs" + ] + }, + "scripts": { + "phpstan": "vendor/bin/phpstan analyse --ansi --error-format symplify", + "check-cs": "vendor/bin/ecs check --ansi", + "fix-cs": "vendor/bin/ecs check --fix --ansi" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..f18d505 --- /dev/null +++ b/config/config.php @@ -0,0 +1,17 @@ +services(); + + $services->defaults() + ->public() + ->autowire() + ->autoconfigure(); + + $services->load('Rector\\', __DIR__ . '/../src') + ->exclude([__DIR__ . '/../src/*/{Rector,ValueObject}']); +}; diff --git a/ecs.php b/ecs.php new file mode 100644 index 0000000..be78624 --- /dev/null +++ b/ecs.php @@ -0,0 +1,27 @@ +parameters(); + + $parameters->set(Option::PATHS, [ + __DIR__ . '/src', + __DIR__ . '/tests', + __DIR__ . '/config', + __DIR__ . '/ecs.php', + ]); + + $parameters->set(Option::SETS, [SetList::PSR_12, SetList::SYMPLIFY, SetList::COMMON, SetList::CLEAN_CODE]); + $parameters->set(Option::SKIP, [ + '*/Source/*', '*/Fixture/*', + + // breaks annotated code - removed on symplify dev-main + \PhpCsFixer\Fixer\ReturnNotation\ReturnAssignmentFixer::class, + ]); + $parameters->set(Option::LINE_ENDING, "\n"); +}; diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..0d8576c --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,58 @@ +includes: + - vendor/symplify/phpstan-extensions/config/config.neon + - vendor/symplify/phpstan-rules/config/symplify-rules.neon + +parameters: + level: max + + paths: + - config + - src + - tests + + scanDirectories: + - stubs + + excludePaths: + - */Source/* + - *Source/* + +# ignoreErrors: +# - +# message: '#Do not inherit from abstract class, better use composition#' +# path: "*Rector.php" +# +# - '#Constant string value need to only have small letters, _, \-, \. and numbers#' +# +# # symfony native values +# - +# message: '#Parameter "(.*?)" cannot be nullable#' +# path: src/Symfony/ValueObject/ServiceDefinition.php +# +# - +# message: '#Use decoupled factory service to create "PHPStan\\Type\\ObjectType" object#' +# path: src/Symfony/ValueObject/ServiceMap/ServiceMap.php +# +# - +# message: '#Parameter "return" cannot be nullable#' +# path: src/Symfony/NodeFactory/ThisRenderFactory.php +# +# # fixed in dev php-parser +# - '#Method Rector\\Symfony\\NodeFactory\\RouteNameClassFactory\:\:create\(\) should return PhpParser\\Node\\Stmt\\Namespace_ but returns PhpParser\\Node#' +# +# # should be fixed on master +# - +# message: '#Do not use chained method calls\. Put each on separated lines#' +# paths: +# - src/Symfony/ConstantNameAndValueMatcher.php +# +# - +# message: '#Parameter "priority" cannot be nullable#' +# paths: +# - src/Symfony/NodeFactory/GetSubscribedEventsClassMethodFactory.php +# +# # good place for value object +# - +# message: '#Use another value object over array with string\-keys and objects, array#' +# paths: +# - src/Symfony3/Rector/MethodCall/FormTypeInstanceToClassConstRector.php diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..1ad9e36 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,13 @@ + + + + + tests + + + diff --git a/src/Nette/Contract/PregToNetteUtilsStringInterface.php b/src/Nette/Contract/PregToNetteUtilsStringInterface.php new file mode 100644 index 0000000..e84a78f --- /dev/null +++ b/src/Nette/Contract/PregToNetteUtilsStringInterface.php @@ -0,0 +1,22 @@ + + */ + public function getTemplateVariables(): array; + + /** + * @return string[] + */ + public function getConditionalVariableNames(): array; +} diff --git a/src/Nette/NodeAnalyzer/ConditionalTemplateAssignReplacer.php b/src/Nette/NodeAnalyzer/ConditionalTemplateAssignReplacer.php new file mode 100644 index 0000000..60358e7 --- /dev/null +++ b/src/Nette/NodeAnalyzer/ConditionalTemplateAssignReplacer.php @@ -0,0 +1,36 @@ +template->key = 'some'; + * } else { + * $this->template->key = 'another'; + * } + * + * ↓ + * + * if (...) { + * $key = 'some'; + * } else { + * $key = 'another'; + * } + */ +final class ConditionalTemplateAssignReplacer +{ + public function processClassMethod(TemplateParametersAssigns $templateParametersAssigns): void + { + foreach ($templateParametersAssigns->getConditionalTemplateParameterAssign() as $conditionalTemplateParameterAssign) { + $assign = $conditionalTemplateParameterAssign->getAssign(); + $assign->var = new Variable($conditionalTemplateParameterAssign->getParameterName()); + } + } +} diff --git a/src/Nette/NodeAnalyzer/NetteClassAnalyzer.php b/src/Nette/NodeAnalyzer/NetteClassAnalyzer.php new file mode 100644 index 0000000..546c92c --- /dev/null +++ b/src/Nette/NodeAnalyzer/NetteClassAnalyzer.php @@ -0,0 +1,47 @@ +nodeTypeResolver = $nodeTypeResolver; + } + + public function isInComponent(Node $node): bool + { + if ($node instanceof Class_) { + $class = $node; + } else { + $class = $node->getAttribute(AttributeKey::CLASS_NODE); + } + + if (! $class instanceof Class_) { + return false; + } + + if (! $this->nodeTypeResolver->isObjectType($class, new ObjectType('Nette\Application\UI\Control'))) { + return false; + } + + if (! $this->nodeTypeResolver->isObjectType($class, new ObjectType('Nette\Application\UI\Control'))) { + return false; + } + + return ! $this->nodeTypeResolver->isObjectType($class, new ObjectType('Nette\Application\UI\Presenter')); + } +} diff --git a/src/Nette/NodeAnalyzer/PregMatchAllAnalyzer.php b/src/Nette/NodeAnalyzer/PregMatchAllAnalyzer.php new file mode 100644 index 0000000..43baf95 --- /dev/null +++ b/src/Nette/NodeAnalyzer/PregMatchAllAnalyzer.php @@ -0,0 +1,39 @@ +args) !== 3) { + return $args; + } + + $constFetch = new ConstFetch(new Name('PREG_SET_ORDER')); + $minus = new Minus($constFetch, new LNumber(1)); + $args[] = new Arg($minus); + + return $args; + } +} diff --git a/src/Nette/NodeAnalyzer/RenderMethodAnalyzer.php b/src/Nette/NodeAnalyzer/RenderMethodAnalyzer.php new file mode 100644 index 0000000..6cd32f1 --- /dev/null +++ b/src/Nette/NodeAnalyzer/RenderMethodAnalyzer.php @@ -0,0 +1,47 @@ +nodeNameResolver = $nodeNameResolver; + $this->betterNodeFinder = $betterNodeFinder; + } + + /** + * @return MethodCall[] + */ + public function machRenderMethodCalls(ClassMethod $classMethod): array + { + /** @var MethodCall[] $methodsCalls */ + $methodsCalls = $this->betterNodeFinder->findInstanceOf((array) $classMethod->stmts, MethodCall::class); + + $renderMethodCalls = []; + foreach ($methodsCalls as $methodCall) { + if ($this->nodeNameResolver->isName($methodCall->name, 'render')) { + $renderMethodCalls[] = $methodCall; + } + } + + return $renderMethodCalls; + } +} diff --git a/src/Nette/NodeAnalyzer/ReturnAnalyzer.php b/src/Nette/NodeAnalyzer/ReturnAnalyzer.php new file mode 100644 index 0000000..b771df5 --- /dev/null +++ b/src/Nette/NodeAnalyzer/ReturnAnalyzer.php @@ -0,0 +1,56 @@ +betterNodeFinder = $betterNodeFinder; + $this->scopeNestingComparator = $scopeNestingComparator; + } + + public function findLastClassMethodReturn(ClassMethod $classMethod): ?Return_ + { + /** @var Return_[] $returns */ + $returns = $this->betterNodeFinder->findInstanceOf($classMethod, Return_::class); + + // put the latest first + $returns = array_reverse($returns); + + foreach ($returns as $return) { + if ($this->scopeNestingComparator->areReturnScopeNested($return, $classMethod)) { + return $return; + } + } + + return null; + } + + public function isBeforeLastReturn(Assign $assign, ?Return_ $lastReturn): bool + { + if (! $lastReturn instanceof Return_) { + return true; + } + + return $lastReturn->getStartTokenPos() < $assign->getStartTokenPos(); + } +} diff --git a/src/Nette/NodeAnalyzer/RightAssignTemplateRemover.php b/src/Nette/NodeAnalyzer/RightAssignTemplateRemover.php new file mode 100644 index 0000000..9166ee3 --- /dev/null +++ b/src/Nette/NodeAnalyzer/RightAssignTemplateRemover.php @@ -0,0 +1,52 @@ +betterNodeFinder = $betterNodeFinder; + $this->thisTemplatePropertyFetchAnalyzer = $thisTemplatePropertyFetchAnalyzer; + $this->nodeRemover = $nodeRemover; + } + + public function removeInClassMethod(ClassMethod $classMethod): void + { + /** @var Assign[] $assigns */ + $assigns = $this->betterNodeFinder->findInstanceOf($classMethod, Assign::class); + + foreach ($assigns as $assign) { + if (! $this->thisTemplatePropertyFetchAnalyzer->isTemplatePropertyFetch($assign->expr)) { + return; + } + + $this->nodeRemover->removeNode($assign); + } + } +} diff --git a/src/Nette/NodeAnalyzer/StaticCallAnalyzer.php b/src/Nette/NodeAnalyzer/StaticCallAnalyzer.php new file mode 100644 index 0000000..d10841f --- /dev/null +++ b/src/Nette/NodeAnalyzer/StaticCallAnalyzer.php @@ -0,0 +1,43 @@ +nodeNameResolver = $nodeNameResolver; + } + + public function isParentCallNamed(Node $node, string $desiredMethodName): bool + { + if (! $node instanceof StaticCall) { + return false; + } + + if ($node->class instanceof Expr) { + return false; + } + + if (! $this->nodeNameResolver->isName($node->class, 'parent')) { + return false; + } + + if ($node->name instanceof Expr) { + return false; + } + + return $this->nodeNameResolver->isName($node->name, $desiredMethodName); + } +} diff --git a/src/Nette/NodeAnalyzer/StrlenEndsWithResolver.php b/src/Nette/NodeAnalyzer/StrlenEndsWithResolver.php new file mode 100644 index 0000000..faaa634 --- /dev/null +++ b/src/Nette/NodeAnalyzer/StrlenEndsWithResolver.php @@ -0,0 +1,79 @@ +nodeNameResolver = $nodeNameResolver; + $this->nodeComparator = $nodeComparator; + } + + /** + * @param Identical|NotIdentical $binaryOp + */ + public function resolveBinaryOpForFunction(BinaryOp $binaryOp): ?ContentExprAndNeedleExpr + { + if ($binaryOp->left instanceof Variable) { + return $this->matchContentExprAndNeedleExpr($binaryOp->right, $binaryOp->left); + } + + if ($binaryOp->right instanceof Variable) { + return $this->matchContentExprAndNeedleExpr($binaryOp->left, $binaryOp->right); + } + + return null; + } + + public function matchContentExprAndNeedleExpr(Node $node, Variable $variable): ?ContentExprAndNeedleExpr + { + if (! $this->nodeNameResolver->isFuncCallName($node, 'substr')) { + return null; + } + + /** @var FuncCall $node */ + if (! $node->args[1]->value instanceof UnaryMinus) { + return null; + } + + /** @var UnaryMinus $unaryMinus */ + $unaryMinus = $node->args[1]->value; + + if (! $this->nodeNameResolver->isFuncCallName($unaryMinus->expr, 'strlen')) { + return null; + } + + /** @var FuncCall $strlenFuncCall */ + $strlenFuncCall = $unaryMinus->expr; + + if ($this->nodeComparator->areNodesEqual($strlenFuncCall->args[0]->value, $variable)) { + return new ContentExprAndNeedleExpr($node->args[0]->value, $strlenFuncCall->args[0]->value); + } + + return null; + } +} diff --git a/src/Nette/NodeAnalyzer/StrlenStartsWithResolver.php b/src/Nette/NodeAnalyzer/StrlenStartsWithResolver.php new file mode 100644 index 0000000..e0bbb47 --- /dev/null +++ b/src/Nette/NodeAnalyzer/StrlenStartsWithResolver.php @@ -0,0 +1,97 @@ +nodeNameResolver = $nodeNameResolver; + $this->valueResolver = $valueResolver; + $this->nodeComparator = $nodeComparator; + } + + /** + * @param Identical|NotIdentical $binaryOp + */ + public function resolveBinaryOpForFunction(BinaryOp $binaryOp, string $functionName): ?ContentExprAndNeedleExpr + { + if ($binaryOp->left instanceof Variable) { + return $this->matchContentExprAndNeedleExpr($binaryOp->right, $binaryOp->left, $functionName); + } + + if ($binaryOp->right instanceof Variable) { + return $this->matchContentExprAndNeedleExpr($binaryOp->left, $binaryOp->right, $functionName); + } + + return null; + } + + private function matchContentExprAndNeedleExpr( + Node $node, Variable $variable, string $functionName + ): ?ContentExprAndNeedleExpr { + if (! $node instanceof FuncCall) { + return null; + } + + if (! $this->nodeNameResolver->isName($node, $functionName)) { + return null; + } + + /** @var FuncCall $node */ + if (! $this->valueResolver->isValue($node->args[1]->value, 0)) { + return null; + } + + if (! isset($node->args[2])) { + return null; + } + + if (! $node->args[2]->value instanceof FuncCall) { + return null; + } + + if (! $this->nodeNameResolver->isName($node->args[2]->value, 'strlen')) { + return null; + } + + /** @var FuncCall $strlenFuncCall */ + $strlenFuncCall = $node->args[2]->value; + if ($this->nodeComparator->areNodesEqual($strlenFuncCall->args[0]->value, $variable)) { + return new ContentExprAndNeedleExpr($node->args[0]->value, $strlenFuncCall->args[0]->value); + } + + return null; + } +} diff --git a/src/Nette/NodeAnalyzer/TemplatePropertyAssignCollector.php b/src/Nette/NodeAnalyzer/TemplatePropertyAssignCollector.php new file mode 100644 index 0000000..c6204d1 --- /dev/null +++ b/src/Nette/NodeAnalyzer/TemplatePropertyAssignCollector.php @@ -0,0 +1,135 @@ +scopeNestingComparator = $scopeNestingComparator; + $this->betterNodeFinder = $betterNodeFinder; + $this->thisTemplatePropertyFetchAnalyzer = $thisTemplatePropertyFetchAnalyzer; + $this->returnAnalyzer = $returnAnalyzer; + } + + public function collect(ClassMethod $classMethod): TemplateParametersAssigns + { + $this->alwaysTemplateParameterAssigns = []; + $this->conditionalTemplateParameterAssigns = []; + + $this->lastReturn = $this->returnAnalyzer->findLastClassMethodReturn($classMethod); + + /** @var Assign[] $assigns */ + $assigns = $this->betterNodeFinder->findInstanceOf((array) $classMethod->stmts, Assign::class); + foreach ($assigns as $assign) { + $this->collectVariableFromAssign($assign); + } + + return new TemplateParametersAssigns( + $this->alwaysTemplateParameterAssigns, + $this->conditionalTemplateParameterAssigns + ); + } + + private function collectVariableFromAssign(Assign $assign): void + { + // $this->template = x + if (! $assign->var instanceof PropertyFetch) { + return; + } + + $parameterName = $this->thisTemplatePropertyFetchAnalyzer->resolveTemplateParameterNameFromAssign($assign); + if ($parameterName === null) { + return; + } + + $propertyFetch = $assign->var; + + $foundParent = $this->betterNodeFinder->findParentTypes( + $propertyFetch->var, + ControlStructure::CONDITIONAL_NODE_SCOPE_TYPES + [FunctionLike::class] + ); + + if ($foundParent && $this->scopeNestingComparator->isInBothIfElseBranch($foundParent, $propertyFetch)) { + $this->conditionalTemplateParameterAssigns[] = new ConditionalTemplateParameterAssign( + $assign, + $parameterName + ); + return; + } + + if ($foundParent instanceof If_) { + return; + } + + if ($foundParent instanceof Else_) { + return; + } + + // there is a return before this assign, to do not remove it and keep ti + if (! $this->returnAnalyzer->isBeforeLastReturn($assign, $this->lastReturn)) { + return; + } + + $this->alwaysTemplateParameterAssigns[] = new AlwaysTemplateParameterAssign( + $assign, + $parameterName, + $assign->expr + ); + } +} diff --git a/src/Nette/NodeAnalyzer/TemplatePropertyParametersReplacer.php b/src/Nette/NodeAnalyzer/TemplatePropertyParametersReplacer.php new file mode 100644 index 0000000..0986408 --- /dev/null +++ b/src/Nette/NodeAnalyzer/TemplatePropertyParametersReplacer.php @@ -0,0 +1,26 @@ +getTemplateParameterAssigns() as $alwaysTemplateParameterAssign) { + $arrayDimFetch = new ArrayDimFetch( + $variable, + new String_($alwaysTemplateParameterAssign->getParameterName()) + ); + + $assign = $alwaysTemplateParameterAssign->getAssign(); + $assign->var = $arrayDimFetch; + } + } +} diff --git a/src/Nette/NodeAnalyzer/ThisTemplatePropertyFetchAnalyzer.php b/src/Nette/NodeAnalyzer/ThisTemplatePropertyFetchAnalyzer.php new file mode 100644 index 0000000..f6a9f2b --- /dev/null +++ b/src/Nette/NodeAnalyzer/ThisTemplatePropertyFetchAnalyzer.php @@ -0,0 +1,81 @@ +nodeNameResolver = $nodeNameResolver; + } + + public function resolveTemplateParameterNameFromAssign(Assign $assign): ?string + { + if (! $assign->var instanceof PropertyFetch) { + return null; + } + + $propertyFetch = $assign->var; + if (! $this->isTemplatePropertyFetch($propertyFetch->var)) { + return null; + } + + return $this->nodeNameResolver->getName($propertyFetch); + } + + /** + * $this->template->someKey => "someKey" + */ + public function matchThisTemplateKey(Expr $expr): ?string + { + if (! $expr instanceof PropertyFetch) { + return null; + } + + if (! $expr->var instanceof PropertyFetch) { + return null; + } + + if (! $this->nodeNameResolver->isName($expr->var, 'template')) { + return null; + } + + return $this->nodeNameResolver->getName($expr->name); + } + + /** + * Looks for: + * $this->template + * + * $template + */ + public function isTemplatePropertyFetch(Expr $expr): bool + { + if (! $expr instanceof PropertyFetch) { + return false; + } + + if (! $expr->var instanceof Variable) { + return false; + } + + if (! $this->nodeNameResolver->isName($expr->var, 'this')) { + return false; + } + + return $this->nodeNameResolver->isName($expr->name, 'template'); + } +} diff --git a/src/Nette/NodeFactory/ActionRenderFactory.php b/src/Nette/NodeFactory/ActionRenderFactory.php new file mode 100644 index 0000000..1d64b6f --- /dev/null +++ b/src/Nette/NodeFactory/ActionRenderFactory.php @@ -0,0 +1,66 @@ +nodeFactory = $nodeFactory; + $this->renderParameterArrayFactory = $renderParameterArrayFactory; + } + + public function createThisRenderMethodCall(ClassMethodRender $classMethodRender): MethodCall + { + $methodCall = $this->nodeFactory->createMethodCall('this', 'render'); + $this->addArguments($classMethodRender, $methodCall); + + return $methodCall; + } + + public function createThisTemplateRenderMethodCall(ClassMethodRender $classMethodRender): MethodCall + { + $thisTemplatePropertyFetch = new PropertyFetch(new Variable('this'), 'template'); + $methodCall = $this->nodeFactory->createMethodCall($thisTemplatePropertyFetch, 'render'); + + $this->addArguments($classMethodRender, $methodCall); + + return $methodCall; + } + + private function addArguments(ClassMethodRender $classMethodRender, MethodCall $methodCall): void + { + if ($classMethodRender->getFirstTemplateFileExpr() !== null) { + $methodCall->args[0] = new Arg($classMethodRender->getFirstTemplateFileExpr()); + } + + $templateVariablesArray = $this->renderParameterArrayFactory->createArray($classMethodRender); + if (! $templateVariablesArray instanceof Array_) { + return; + } + + $methodCall->args[1] = new Arg($templateVariablesArray); + } +} diff --git a/src/Nette/NodeFactory/CheckRequirementsClassMethodFactory.php b/src/Nette/NodeFactory/CheckRequirementsClassMethodFactory.php new file mode 100644 index 0000000..290c161 --- /dev/null +++ b/src/Nette/NodeFactory/CheckRequirementsClassMethodFactory.php @@ -0,0 +1,61 @@ +parentGetterStmtsToExternalStmtsFactory = $parentGetterStmtsToExternalStmtsFactory; + } + + /** + * @param Node[] $getUserStmts + */ + public function create(array $getUserStmts): ClassMethod + { + $methodBuilder = new MethodBuilder(self::CHECK_REQUIREMENTS_METHOD_NAME); + $methodBuilder->makePublic(); + + $paramBuilder = new ParamBuilder('element'); + $methodBuilder->addParam($paramBuilder); + $methodBuilder->setReturnType('void'); + + $parentStaticCall = $this->creatParentStaticCall(); + + $newStmts = $this->parentGetterStmtsToExternalStmtsFactory->create($getUserStmts); + $methodBuilder->addStmts($newStmts); + + $methodBuilder->addStmt($parentStaticCall); + + return $methodBuilder->getNode(); + } + + private function creatParentStaticCall(): StaticCall + { + $args = [new Arg(new Variable('element'))]; + + return new StaticCall(new Name('parent'), new Identifier(self::CHECK_REQUIREMENTS_METHOD_NAME), $args); + } +} diff --git a/src/Nette/NodeFactory/ParentGetterStmtsToExternalStmtsFactory.php b/src/Nette/NodeFactory/ParentGetterStmtsToExternalStmtsFactory.php new file mode 100644 index 0000000..f0d26d8 --- /dev/null +++ b/src/Nette/NodeFactory/ParentGetterStmtsToExternalStmtsFactory.php @@ -0,0 +1,115 @@ +nodeTypeResolver = $nodeTypeResolver; + $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->nodeComparator = $nodeComparator; + } + + /** + * @param Node[] $getUserStmts + * @return Node[] + */ + public function create(array $getUserStmts): array + { + $userExpression = null; + + foreach ($getUserStmts as $key => $getUserStmt) { + if (! $getUserStmt instanceof Expression) { + continue; + } + + $getUserStmt = $getUserStmt->expr; + if (! $getUserStmt instanceof Assign) { + continue; + } + + if (! $getUserStmt->expr instanceof StaticCall) { + continue; + } + + if (! $this->nodeTypeResolver->isObjectType( + $getUserStmt->expr, + new ObjectType('Nette\Security\User') + )) { + continue; + } + + $userExpression = $getUserStmt->var; + unset($getUserStmts[$key]); + } + + $getUserStmts = $this->removeReturn($getUserStmts); + + // nothing we can do + if ($userExpression === null) { + return []; + } + + // stmts without assign + $this->simpleCallableNodeTraverser->traverseNodesWithCallable($getUserStmts, function (Node $node) use ( + $userExpression + ): ?MethodCall { + if (! $this->nodeComparator->areNodesEqual($node, $userExpression)) { + return null; + } + + return new MethodCall(new Variable('this'), 'getUser'); + }); + + return $getUserStmts; + } + + /** + * @param Node[] $stmts + * @return Node[] + */ + private function removeReturn(array $stmts): array + { + foreach ($stmts as $key => $stmt) { + if (! $stmt instanceof Return_) { + continue; + } + + unset($stmts[$key]); + } + + return $stmts; + } +} diff --git a/src/Nette/NodeFactory/RenderParameterArrayFactory.php b/src/Nette/NodeFactory/RenderParameterArrayFactory.php new file mode 100644 index 0000000..bc06bc7 --- /dev/null +++ b/src/Nette/NodeFactory/RenderParameterArrayFactory.php @@ -0,0 +1,32 @@ +getTemplateVariables() as $name => $expr) { + $arrayItems[] = new ArrayItem($expr, new String_($name)); + } + + foreach ($parameterArray->getConditionalVariableNames() as $variableName) { + $arrayItems[] = new ArrayItem(new Variable($variableName), new String_($variableName)); + } + + if ($arrayItems === []) { + return null; + } + + return new Array_($arrayItems); + } +} diff --git a/src/Nette/NodeFinder/ParamFinder.php b/src/Nette/NodeFinder/ParamFinder.php new file mode 100644 index 0000000..986c1a8 --- /dev/null +++ b/src/Nette/NodeFinder/ParamFinder.php @@ -0,0 +1,48 @@ +betterNodeFinder = $betterNodeFinder; + $this->nodeComparator = $nodeComparator; + } + + /** + * @param Node|Node[] $nodeHaystack + */ + public function isInAssign($nodeHaystack, Param $param): bool + { + $variable = $param->var; + + return (bool) $this->betterNodeFinder->find($nodeHaystack, function (Node $node) use ($variable): bool { + $parent = $node->getAttribute(AttributeKey::PARENT_NODE); + if (! $parent instanceof Assign) { + return false; + } + + return $this->nodeComparator->areNodesEqual($node, $variable); + }); + } +} diff --git a/src/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector.php b/src/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector.php new file mode 100644 index 0000000..c4dbcd7 --- /dev/null +++ b/src/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector.php @@ -0,0 +1,258 @@ +staticCallAnalyzer = $staticCallAnalyzer; + $this->methodReflectionProvider = $methodReflectionProvider; + $this->paramFinder = $paramFinder; + + $this->controlObjectType = new ObjectType('Nette\Application\UI\Control'); + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Remove $parent and $name in control constructor', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +class SomeControl extends Control +{ + public function __construct(IContainer $parent = null, $name = null, int $value) + { + parent::__construct($parent, $name); + $this->value = $value; + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +class SomeControl extends Control +{ + public function __construct(int $value) + { + $this->value = $value; + } +} +CODE_SAMPLE + + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, StaticCall::class, New_::class]; + } + + /** + * @param ClassMethod|StaticCall|New_ $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof ClassMethod) { + return $this->refactorClassMethod($node); + } + + if ($node instanceof StaticCall) { + return $this->refactorStaticCall($node); + } + + if ($this->isObjectType($node->class, new ObjectType('Nette\Application\UI\Control'))) { + return $this->refactorNew($node); + } + + return null; + } + + private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod + { + if (! $this->isInsideNetteComponentClass($classMethod)) { + return null; + } + + if (! $this->isName($classMethod, MethodName::CONSTRUCT)) { + return null; + } + + return $this->removeClassMethodParams($classMethod); + } + + private function refactorStaticCall(StaticCall $staticCall): ?StaticCall + { + if (! $this->isInsideNetteComponentClass($staticCall)) { + return null; + } + + if (! $this->staticCallAnalyzer->isParentCallNamed($staticCall, MethodName::CONSTRUCT)) { + return null; + } + + foreach ($staticCall->args as $staticCallArg) { + if (! $staticCallArg->value instanceof Variable) { + continue; + } + + /** @var Variable $variable */ + $variable = $staticCallArg->value; + if (! $this->isNames($variable, [self::NAME, self::PARENT])) { + continue; + } + + $this->removeNode($staticCallArg); + } + + if ($this->shouldRemoveEmptyCall($staticCall)) { + $this->removeNode($staticCall); + return null; + } + + return $staticCall; + } + + private function refactorNew(New_ $new): New_ + { + $parameterNames = $this->methodReflectionProvider->provideParameterNamesByNew($new); + + foreach ($new->args as $position => $arg) { + // is on position of $parent or $name? + if (! isset($parameterNames[$position])) { + continue; + } + + $parameterName = $parameterNames[$position]; + if (! in_array($parameterName, [self::PARENT, self::NAME], true)) { + continue; + } + + $this->removeNode($arg); + } + + return $new; + } + + private function isInsideNetteComponentClass(Node $node): bool + { + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return false; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + throw new ShouldNotHappenException(); + } + + // presenter is not a component + if ($classReflection->isSubclassOf('Nette\Application\UI\Presenter')) { + return false; + } + + return $classReflection->isSubclassOf($this->controlObjectType->getClassName()); + } + + private function removeClassMethodParams(ClassMethod $classMethod): ClassMethod + { + foreach ($classMethod->params as $param) { + if ($this->paramFinder->isInAssign((array) $classMethod->stmts, $param)) { + continue; + } + + if ($this->isObjectType($param, new ObjectType('Nette\ComponentModel\IContainer'))) { + $this->removeNode($param); + continue; + } + + if ($this->isName($param, self::NAME)) { + $this->removeNode($param); + } + } + + return $classMethod; + } + + private function shouldRemoveEmptyCall(StaticCall $staticCall): bool + { + foreach ($staticCall->args as $arg) { + if ($this->nodesToRemoveCollector->isNodeRemoved($arg)) { + continue; + } + + return false; + } + + return true; + } +} diff --git a/src/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector.php b/src/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector.php new file mode 100644 index 0000000..9e4114a --- /dev/null +++ b/src/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector.php @@ -0,0 +1,224 @@ +templatePropertyAssignCollector = $templatePropertyAssignCollector; + $this->renderMethodAnalyzer = $renderMethodAnalyzer; + $this->netteClassAnalyzer = $netteClassAnalyzer; + $this->renderParameterArrayFactory = $renderParameterArrayFactory; + $this->conditionalTemplateAssignReplacer = $conditionalTemplateAssignReplacer; + $this->rightAssignTemplateRemover = $rightAssignTemplateRemover; + $this->templatePropertyParametersReplacer = $templatePropertyParametersReplacer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change `$this->templates->{magic}` to `$this->template->render(..., $values)` in components', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +class SomeControl extends Control +{ + public function render() + { + $this->template->param = 'some value'; + $this->template->render(__DIR__ . '/poll.latte'); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +class SomeControl extends Control +{ + public function render() + { + $this->template->render(__DIR__ . '/poll.latte', ['param' => 'some value']); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkip($node)) { + return null; + } + + $renderMethodCalls = $this->renderMethodAnalyzer->machRenderMethodCalls($node); + if ($renderMethodCalls === []) { + return null; + } + + if (! $this->haveMethodCallsFirstArgument($renderMethodCalls)) { + return null; + } + + // initialize $parameters variable for multiple renders + if (count($renderMethodCalls) > 1) { + return $this->refactorForMultipleRenderMethodCalls($node, $renderMethodCalls); + } + + return $this->refactorForSingleRenderMethodCall($node, $renderMethodCalls[0]); + } + + private function shouldSkip(ClassMethod $classMethod): bool + { + if (! $this->isNames($classMethod, ['render', 'render*'])) { + return true; + } + + return ! $this->netteClassAnalyzer->isInComponent($classMethod); + } + + /** + * @param MethodCall[] $methodCalls + */ + private function haveMethodCallsFirstArgument(array $methodCalls): bool + { + foreach ($methodCalls as $methodCall) { + if (! isset($methodCall->args[0])) { + return false; + } + } + + return true; + } + + private function refactorForSingleRenderMethodCall( + ClassMethod $classMethod, + MethodCall $renderMethodCall + ): ?ClassMethod { + $templateParametersAssigns = $this->templatePropertyAssignCollector->collect($classMethod); + + $array = $this->renderParameterArrayFactory->createArray($templateParametersAssigns); + if (! $array instanceof Array_) { + return null; + } + + $this->conditionalTemplateAssignReplacer->processClassMethod($templateParametersAssigns); + $renderMethodCall->args[1] = new Arg($array); + + foreach ($templateParametersAssigns->getTemplateParameterAssigns() as $alwaysTemplateParameterAssign) { + $this->removeNode($alwaysTemplateParameterAssign->getAssign()); + } + + $this->rightAssignTemplateRemover->removeInClassMethod($classMethod); + + return $classMethod; + } + + /** + * @param MethodCall[] $renderMethodCalls + */ + private function refactorForMultipleRenderMethodCalls( + ClassMethod $classMethod, + array $renderMethodCalls + ): ?ClassMethod { + $magicTemplateParametersAssigns = $this->templatePropertyAssignCollector->collect($classMethod); + + if ($magicTemplateParametersAssigns->getTemplateParameterAssigns() === []) { + return null; + } + + $parametersVariable = new Variable('parameters'); + $parametersAssign = new Assign($parametersVariable, new Array_()); + $assignExpression = new Expression($parametersAssign); + $classMethod->stmts = array_merge([$assignExpression], (array) $classMethod->stmts); + + $this->templatePropertyParametersReplacer->replace($magicTemplateParametersAssigns, $parametersVariable); + + foreach ($renderMethodCalls as $renderMethodCall) { + $renderMethodCall->args[1] = new Arg($parametersVariable); + } + + return $classMethod; + } +} diff --git a/src/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector.php b/src/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector.php new file mode 100644 index 0000000..227e946 --- /dev/null +++ b/src/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector.php @@ -0,0 +1,150 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->nodeTypeResolver->isMethodStaticCallOrClassMethodObjectType( + $node, + new ObjectType('Nette\Localization\ITranslator') + )) { + return null; + } + + if (! $this->isName($node->name, 'translate')) { + return null; + } + + if (! isset($node->params[1])) { + return null; + } + + $secondParam = $node->params[1]; + if (! $secondParam->var instanceof Variable) { + return null; + } + + if ($secondParam->variadic) { + return null; + } + + $this->replaceSecondParamInClassMethodBody($node, $secondParam); + + $secondParam->default = null; + $secondParam->variadic = true; + $secondParam->var->name = self::PARAMETERS; + + return $node; + } + + private function replaceSecondParamInClassMethodBody(ClassMethod $classMethod, Param $param): void + { + $paramName = $this->getName($param->var); + if ($paramName === null) { + return; + } + + $this->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) use ($paramName): ?int { + if (! $node instanceof Variable) { + return null; + } + + if (! $this->isName($node, $paramName)) { + return null; + } + + // instantiate + $assign = $this->createCoalesceAssign($node); + + $currentStmt = $node->getAttribute(AttributeKey::CURRENT_STATEMENT); + $positionNode = $currentStmt ?? $node; + $this->addNodeBeforeNode($assign, $positionNode); + + return NodeTraverser::STOP_TRAVERSAL; + }); + } + + private function createCoalesceAssign(Variable $variable): Assign + { + $arrayDimFetch = new ArrayDimFetch(new Variable(self::PARAMETERS), new LNumber(0)); + $coalesce = new Coalesce($arrayDimFetch, $this->nodeFactory->createNull()); + + return new Assign(new Variable($variable->name), $coalesce); + } +} diff --git a/src/Nette/Rector/Class_/MoveFinalGetUserToCheckRequirementsClassMethodRector.php b/src/Nette/Rector/Class_/MoveFinalGetUserToCheckRequirementsClassMethodRector.php new file mode 100644 index 0000000..ee90cff --- /dev/null +++ b/src/Nette/Rector/Class_/MoveFinalGetUserToCheckRequirementsClassMethodRector.php @@ -0,0 +1,118 @@ +checkRequirementsClassMethodFactory = $checkRequirementsClassMethodFactory; + $this->classInsertManipulator = $classInsertManipulator; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Presenter method getUser() is now final, move logic to checkRequirements()', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Presenter; + +class SomeControl extends Presenter +{ + public function getUser() + { + $user = parent::getUser(); + $user->getStorage()->setNamespace('admin_session'); + return $user; + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Nette\Application\UI\Presenter; + +class SomeControl extends Presenter +{ + public function checkRequirements() + { + $user = $this->getUser(); + $user->getStorage()->setNamespace('admin_session'); + + parent::checkRequirements(); + } +} +CODE_SAMPLE + + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node, new ObjectType('Nette\Application\UI\Presenter'))) { + return null; + } + + $getUserClassMethod = $node->getMethod('getUser'); + if (! $getUserClassMethod instanceof ClassMethod) { + return null; + } + + $checkRequirementsClassMethod = $node->getMethod('checkRequirements'); + if ($checkRequirementsClassMethod === null) { + $checkRequirementsClassMethod = $this->checkRequirementsClassMethodFactory->create( + (array) $getUserClassMethod->stmts + ); + + $this->classInsertManipulator->addAsFirstMethod($node, $checkRequirementsClassMethod); + } else { + throw new NotImplementedYetException(); + } + + $this->removeNode($getUserClassMethod); + + return $node; + } +} diff --git a/src/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector.php b/src/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector.php new file mode 100644 index 0000000..af8abb3 --- /dev/null +++ b/src/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector.php @@ -0,0 +1,74 @@ +> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isName($node, 'file_put_contents')) { + return null; + } + + if (count($node->args) !== 2) { + return null; + } + + return $this->nodeFactory->createStaticCall('Nette\Utils\FileSystem', 'write', $node->args); + } +} diff --git a/src/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector.php b/src/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector.php new file mode 100644 index 0000000..727d0db --- /dev/null +++ b/src/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector.php @@ -0,0 +1,126 @@ +> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?Node + { + if ($this->isName($node, 'json_encode')) { + return $this->refactorJsonEncode($node); + } + + if ($this->isName($node, 'json_decode')) { + return $this->refactorJsonDecode($node); + } + + return null; + } + + private function refactorJsonEncode(FuncCall $funcCall): StaticCall + { + $args = $funcCall->args; + if (isset($args[1])) { + $secondArgumentValue = $args[1]->value; + + if ($this->isName($secondArgumentValue, 'JSON_PRETTY_PRINT')) { + $classConstFetch = $this->nodeFactory->createClassConstFetch('Nette\Utils\Json', 'PRETTY'); + $args[1] = new Arg($classConstFetch); + } + } + + return $this->nodeFactory->createStaticCall('Nette\Utils\Json', 'encode', $args); + } + + private function refactorJsonDecode(FuncCall $funcCall): StaticCall + { + $args = $funcCall->args; + + if (isset($args[1])) { + $secondArgumentValue = $args[1]->value; + + if ($this->valueResolver->isFalse($secondArgumentValue)) { + unset($args[1]); + } elseif ($this->valueResolver->isTrue($secondArgumentValue)) { + $classConstFetch = $this->nodeFactory->createClassConstFetch('Nette\Utils\Json', 'FORCE_ARRAY'); + $args[1] = new Arg($classConstFetch); + } + } + + return $this->nodeFactory->createStaticCall('Nette\Utils\Json', 'decode', $args); + } +} diff --git a/src/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector.php b/src/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector.php new file mode 100644 index 0000000..183d830 --- /dev/null +++ b/src/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector.php @@ -0,0 +1,231 @@ + + */ + private const FUNCTION_NAME_TO_METHOD_NAME = [ + 'preg_split' => 'split', + 'preg_replace' => 'replace', + 'preg_replace_callback' => 'replace', + ]; + + /** + * @see https://regex101.com/r/05MPWa/1/ + * @var string + */ + private const SLASH_REGEX = '#[^\\\\]\(#'; + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Use Nette\Utils\Strings over bare preg_split() and preg_replace() functions', + [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + public function run() + { + $content = 'Hi my name is Tom'; + $splitted = preg_split('#Hi#', $content); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Nette\Utils\Strings; + +class SomeClass +{ + public function run() + { + $content = 'Hi my name is Tom'; + $splitted = \Nette\Utils\Strings::split($content, '#Hi#'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @param FuncCall|Identical $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof Identical) { + return $this->refactorIdentical($node); + } + + return $this->refactorFuncCall($node); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [FuncCall::class, Identical::class]; + } + + public function refactorIdentical(Identical $identical): ?Bool_ + { + $parentNode = $identical->getAttribute(AttributeKey::PARENT_NODE); + + if ($identical->left instanceof FuncCall) { + $refactoredFuncCall = $this->refactorFuncCall($identical->left); + if ($refactoredFuncCall !== null && $this->valueResolver->isValue($identical->right, 1)) { + return $this->createBoolCast($parentNode, $refactoredFuncCall); + } + } + + if ($identical->right instanceof FuncCall) { + $refactoredFuncCall = $this->refactorFuncCall($identical->right); + if ($refactoredFuncCall !== null && $this->valueResolver->isValue($identical->left, 1)) { + return new Bool_($refactoredFuncCall); + } + } + + return null; + } + + /** + * @return FuncCall|StaticCall|Assign|null + */ + public function refactorFuncCall(FuncCall $funcCall): ?Expr + { + $methodName = $this->nodeNameResolver->matchNameFromMap($funcCall, self::FUNCTION_NAME_TO_METHOD_NAME); + if ($methodName === null) { + return null; + } + + $matchStaticCall = $this->createMatchStaticCall($funcCall, $methodName); + + // skip assigns, might be used with different return value + $parentNode = $funcCall->getAttribute(AttributeKey::PARENT_NODE); + + if ($parentNode instanceof Assign) { + if ($methodName === 'split') { + return $this->processSplit($funcCall, $matchStaticCall); + } + + if ($methodName === 'replace') { + return $matchStaticCall; + } + + return null; + } + + $currentFunctionName = $this->getName($funcCall); + + // assign + if (isset($funcCall->args[2]) && $currentFunctionName !== 'preg_replace') { + return new Assign($funcCall->args[2]->value, $matchStaticCall); + } + + return $matchStaticCall; + } + + /** + * @param Expr $expr + */ + private function createBoolCast(?Node $node, Node $expr): Bool_ + { + if ($node instanceof Return_ && $expr instanceof Assign) { + $expr = $expr->expr; + } + + return new Bool_($expr); + } + + private function createMatchStaticCall(FuncCall $funcCall, string $methodName): StaticCall + { + $args = []; + + if ($methodName === 'replace') { + $args[] = $funcCall->args[2]; + $args[] = $funcCall->args[0]; + $args[] = $funcCall->args[1]; + } else { + $args[] = $funcCall->args[1]; + $args[] = $funcCall->args[0]; + } + + return $this->nodeFactory->createStaticCall('Nette\Utils\Strings', $methodName, $args); + } + + /** + * @return FuncCall|StaticCall + */ + private function processSplit(FuncCall $funcCall, StaticCall $matchStaticCall): Expr + { + $matchStaticCall = $this->compensateNetteUtilsSplitDelimCapture($matchStaticCall); + + if (! isset($funcCall->args[2])) { + return $matchStaticCall; + } + + if ($this->valueResolver->isValue($funcCall->args[2]->value, -1)) { + if (isset($funcCall->args[3])) { + $matchStaticCall->args[] = $funcCall->args[3]; + } + + return $matchStaticCall; + } + + return $funcCall; + } + + /** + * Handles https://github.com/rectorphp/rector/issues/2348 + */ + private function compensateNetteUtilsSplitDelimCapture(StaticCall $staticCall): StaticCall + { + $patternValue = $this->valueResolver->getValue($staticCall->args[1]->value); + if (! is_string($patternValue)) { + return $staticCall; + } + + $match = Strings::match($patternValue, self::SLASH_REGEX); + if ($match === null) { + return $staticCall; + } + + $constFetch = new ConstFetch(new Name('PREG_SPLIT_DELIM_CAPTURE')); + $bitwiseAnd = new BitwiseAnd(new LNumber(0), new BitwiseNot($constFetch)); + $staticCall->args[2] = new Arg($bitwiseAnd); + + return $staticCall; + } +} diff --git a/src/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector.php b/src/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector.php new file mode 100644 index 0000000..9e2324c --- /dev/null +++ b/src/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector.php @@ -0,0 +1,174 @@ + + */ + private const FUNCTION_NAME_TO_METHOD_NAME = [ + 'preg_match' => 'match', + 'preg_match_all' => 'matchAll', + ]; + + /** + * @var PregMatchAllAnalyzer + */ + private $pregMatchAllAnalyzer; + + public function __construct(PregMatchAllAnalyzer $pregMatchAllAnalyzer) + { + $this->pregMatchAllAnalyzer = $pregMatchAllAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Use Nette\Utils\Strings over bare preg_match() and preg_match_all() functions', + [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + public function run() + { + $content = 'Hi my name is Tom'; + preg_match('#Hi#', $content, $matches); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Nette\Utils\Strings; + +class SomeClass +{ + public function run() + { + $content = 'Hi my name is Tom'; + $matches = Strings::match($content, '#Hi#'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @param FuncCall|Identical $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof Identical) { + return $this->refactorIdentical($node); + } + + return $this->refactorFuncCall($node); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [FuncCall::class, Identical::class]; + } + + public function refactorIdentical(Identical $identical): ?Bool_ + { + $parentNode = $identical->getAttribute(AttributeKey::PARENT_NODE); + + if ($identical->left instanceof FuncCall) { + $refactoredFuncCall = $this->refactorFuncCall($identical->left); + if ($refactoredFuncCall !== null && $this->valueResolver->isValue($identical->right, 1)) { + return $this->createBoolCast($parentNode, $refactoredFuncCall); + } + } + + if ($identical->right instanceof FuncCall) { + $refactoredFuncCall = $this->refactorFuncCall($identical->right); + if ($refactoredFuncCall !== null && $this->valueResolver->isValue($identical->left, 1)) { + return $this->createBoolCast($parentNode, $refactoredFuncCall); + } + } + + return null; + } + + /** + * @return FuncCall|StaticCall|Assign|null + */ + public function refactorFuncCall(FuncCall $funcCall): ?Expr + { + $methodName = $this->nodeNameResolver->matchNameFromMap($funcCall, self::FUNCTION_NAME_TO_METHOD_NAME); + if ($methodName === null) { + return null; + } + + $matchStaticCall = $this->createMatchStaticCall($funcCall, $methodName); + + // skip assigns, might be used with different return value + $parentNode = $funcCall->getAttribute(AttributeKey::PARENT_NODE); + if ($parentNode instanceof Assign) { + if ($methodName === 'matchAll') { + // use count + return new FuncCall(new Name('count'), [new Arg($matchStaticCall)]); + } + + return null; + } + + // assign + if (isset($funcCall->args[2])) { + return new Assign($funcCall->args[2]->value, $matchStaticCall); + } + + return $matchStaticCall; + } + + /** + * @param Expr $expr + */ + private function createBoolCast(?Node $node, Node $expr): Bool_ + { + if ($node instanceof Return_ && $expr instanceof Assign) { + $expr = $expr->expr; + } + + return new Bool_($expr); + } + + private function createMatchStaticCall(FuncCall $funcCall, string $methodName): StaticCall + { + $args = []; + $args[] = $funcCall->args[1]; + $args[] = $funcCall->args[0]; + + $args = $this->pregMatchAllAnalyzer->compensateEnforcedFlag($methodName, $funcCall, $args); + return $this->nodeFactory->createStaticCall('Nette\Utils\Strings', $methodName, $args); + } +} diff --git a/src/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector.php b/src/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector.php new file mode 100644 index 0000000..7416efc --- /dev/null +++ b/src/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector.php @@ -0,0 +1,78 @@ + + */ + private const FUNCTION_TO_STATIC_METHOD = [ + 'substr' => 'substring', + ]; + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Use Nette\Utils\Strings over bare string-functions', + [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + public function run() + { + return substr($value, 0, 3); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeClass +{ + public function run() + { + return \Nette\Utils\Strings::substring($value, 0, 3); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): ?Node + { + foreach (self::FUNCTION_TO_STATIC_METHOD as $function => $staticMethod) { + if (! $this->isName($node, $function)) { + continue; + } + + return $this->nodeFactory->createStaticCall('Nette\Utils\Strings', $staticMethod, $node->args); + } + + return null; + } +} diff --git a/src/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector.php b/src/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector.php new file mode 100644 index 0000000..f58b36c --- /dev/null +++ b/src/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector.php @@ -0,0 +1,96 @@ +strlenEndsWithResolver = $strlenEndsWithResolver; + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Identical::class, NotIdentical::class]; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Use Nette\Utils\Strings::endsWith() over bare string-functions', + [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + public function end($needle) + { + $content = 'Hi, my name is Tom'; + + $yes = substr($content, -strlen($needle)) === $needle; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Nette\Utils\Strings; + +class SomeClass +{ + public function end($needle) + { + $content = 'Hi, my name is Tom'; + $yes = Strings::endsWith($content, $needle); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @param Identical|NotIdentical $node + */ + public function refactor(Node $node): ?Node + { + $contentExprAndNeedleExpr = $this->strlenEndsWithResolver->resolveBinaryOpForFunction($node); + if (! $contentExprAndNeedleExpr instanceof ContentExprAndNeedleExpr) { + return null; + } + + $staticCall = $this->nodeFactory->createStaticCall('Nette\Utils\Strings', 'endsWith', [ + $contentExprAndNeedleExpr->getContentExpr(), + $contentExprAndNeedleExpr->getNeedleExpr(), + ]); + + if ($node instanceof NotIdentical) { + return new BooleanNot($staticCall); + } + + return $staticCall; + } +} diff --git a/src/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector.php b/src/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector.php new file mode 100644 index 0000000..d402118 --- /dev/null +++ b/src/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector.php @@ -0,0 +1,95 @@ +strlenStartsWithResolver = $strlenStartsWithResolver; + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Identical::class, NotIdentical::class]; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Use Nette\Utils\Strings::startsWith() over bare string-functions', [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ +public function start($needle) +{ + $content = 'Hi, my name is Tom'; + $yes = substr($content, 0, strlen($needle)) === $needle; +} +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Nette\Utils\Strings; + +class SomeClass +{ +public function start($needle) +{ + $content = 'Hi, my name is Tom'; + $yes = Strings::startsWith($content, $needle); +} +} +CODE_SAMPLE + ), + ]); + } + + /** + * @param Identical|NotIdentical $node + */ + public function refactor(Node $node): ?Node + { + $contentExprAndNeedleExpr = $this->strlenStartsWithResolver->resolveBinaryOpForFunction($node, 'substr'); + + if (! $contentExprAndNeedleExpr instanceof ContentExprAndNeedleExpr) { + return null; + } + + $staticCall = $this->nodeFactory->createStaticCall('Nette\Utils\Strings', 'startsWith', [ + $contentExprAndNeedleExpr->getContentExpr(), + $contentExprAndNeedleExpr->getNeedleExpr(), + ]); + + if ($node instanceof NotIdentical) { + return new BooleanNot($staticCall); + } + + return $staticCall; + } +} diff --git a/src/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector.php b/src/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector.php new file mode 100644 index 0000000..a82753c --- /dev/null +++ b/src/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector.php @@ -0,0 +1,82 @@ + + */ + private const NUMBER_TO_CONSTANT_NAME = [ + DateTime::HOUR => 'HOUR', + DateTime::DAY => 'DAY', + DateTime::WEEK => 'WEEK', + DateTime::MONTH => 'MONTH', + DateTime::YEAR => 'YEAR', + ]; + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Replace time numbers with Nette\Utils\DateTime constants', + [ + new CodeSample( + <<<'CODE_SAMPLE' +final class SomeClass +{ + public function run() + { + return 86400; + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +final class SomeClass +{ + public function run() + { + return \Nette\Utils\DateTime::DAY; + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [LNumber::class]; + } + + /** + * @param LNumber $node + */ + public function refactor(Node $node): ?Node + { + $number = $node->value; + + $constantName = self::NUMBER_TO_CONSTANT_NAME[$number] ?? null; + if ($constantName === null) { + return null; + } + + return $this->nodeFactory->createClassConstFetch('Nette\Utils\DateTime', $constantName); + } +} diff --git a/src/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector.php b/src/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector.php new file mode 100644 index 0000000..7515f37 --- /dev/null +++ b/src/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector.php @@ -0,0 +1,160 @@ +addDatePicker('key', 'Label'); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\Application\UI\Form; + +class SomeClass +{ + public function run() + { + $form = new Form(); + $form['key'] = new \Nextras\FormComponents\Controls\DateControl('Label'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + // 1. chain call + if ($node->var instanceof MethodCall) { + if (! $this->isOnClassMethodCall( + $node->var, + new ObjectType('Nette\Application\UI\Form'), + 'addDatePicker' + )) { + return null; + } + + $assign = $this->createAssign($node->var); + if (! $assign instanceof Node) { + return null; + } + + $controlName = $this->resolveControlName($node->var); + $node->var = new Variable($controlName); + + // this fixes printing indent + $node->setAttribute(AttributeKey::ORIGINAL_NODE, null); + $this->addNodeBeforeNode($assign, $node); + + return $node; + } + + // 2. assign call + if (! $this->isOnClassMethodCall($node, new ObjectType('Nette\Application\UI\Form'), 'addDatePicker')) { + return null; + } + + return $this->createAssign($node); + } + + private function createAssign(MethodCall $methodCall): ?Node + { + $key = $methodCall->args[0]->value; + if (! $key instanceof String_) { + return null; + } + + $new = $this->createDateTimeControlNew($methodCall); + + $parent = $methodCall->getAttribute(AttributeKey::PARENT_NODE); + if ($parent instanceof Assign) { + return $new; + } + + $arrayDimFetch = new ArrayDimFetch($methodCall->var, $key); + $new = $this->createDateTimeControlNew($methodCall); + + $formAssign = new Assign($arrayDimFetch, $new); + + if ($parent !== null) { + $methodCalls = $this->betterNodeFinder->findInstanceOf($parent, MethodCall::class); + + if (count($methodCalls) > 1) { + $controlName = $this->resolveControlName($methodCall); + return new Assign(new Variable($controlName), $formAssign); + } + } + + return $formAssign; + } + + private function resolveControlName(MethodCall $methodCall): string + { + $controlName = $methodCall->args[0]->value; + if (! $controlName instanceof String_) { + throw new ShouldNotHappenException(); + } + + return $controlName->value . 'DateControl'; + } + + private function createDateTimeControlNew(MethodCall $methodCall): New_ + { + $fullyQualified = new FullyQualified('Nextras\FormComponents\Controls\DateControl'); + $new = new New_($fullyQualified); + + if (isset($methodCall->args[1])) { + $new->args[] = $methodCall->args[1]; + } + + return $new; + } +} diff --git a/src/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector.php b/src/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector.php new file mode 100644 index 0000000..8010573 --- /dev/null +++ b/src/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector.php @@ -0,0 +1,86 @@ +expand() to static call with parameters', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\DI\CompilerExtension; + +final class SomeClass extends CompilerExtension +{ + public function loadConfiguration() + { + $value = $this->getContainerBuilder()->expand('%value'); + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Nette\DI\CompilerExtension; + +final class SomeClass extends CompilerExtension +{ + public function loadConfiguration() + { + $value = \Nette\DI\Helpers::expand('%value', $this->getContainerBuilder()->parameters); + } +} +CODE_SAMPLE + + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isOnClassMethodCall($node, new ObjectType('Nette\DI\ContainerBuilder'), 'expand')) { + return null; + } + + $args = $node->args; + $getContainerBuilderMethodCall = new MethodCall(new Variable('this'), 'getContainerBuilder'); + $parametersPropertyFetch = new PropertyFetch($getContainerBuilderMethodCall, 'parameters'); + + $args[] = new Arg($parametersPropertyFetch); + + return new StaticCall(new FullyQualified('Nette\DI\Helpers'), 'expand', $args); + } +} diff --git a/src/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector.php b/src/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector.php new file mode 100644 index 0000000..c2f753d --- /dev/null +++ b/src/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector.php @@ -0,0 +1,115 @@ +testsNodeAnalyzer = $testsNodeAnalyzer; + $this->dependencyInjectionMethodCallAnalyzer = $dependencyInjectionMethodCallAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Move dependency get via $context->getByType() to constructor injection', + [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @var \Nette\DI\Container + */ + private $context; + + public function run() + { + $someTypeToInject = $this->context->getByType(SomeTypeToInject::class); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @var \Nette\DI\Container + */ + private $context; + + public function __construct(private SomeTypeToInject $someTypeToInject) + { + } + + public function run() + { + $someTypeToInject = $this->someTypeToInject; + } +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if ($this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + if (! $node->var instanceof PropertyFetch) { + return null; + } + + if (! $this->isObjectType($node->var, new ObjectType('Nette\DI\Container'))) { + return null; + } + + if (! $this->isName($node->name, 'getByType')) { + return null; + } + + return $this->dependencyInjectionMethodCallAnalyzer->replaceMethodCallWithPropertyFetchAndDependency($node); + } +} diff --git a/src/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector.php b/src/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector.php new file mode 100644 index 0000000..b364dd1 --- /dev/null +++ b/src/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector.php @@ -0,0 +1,75 @@ +addUpload('...', '...', true); +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +$form = new Nette\Forms\Form(); +$form->addMultiUpload('...', '...'); +CODE_SAMPLE + + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node->var, new ObjectType('Nette\Forms\Form'))) { + return null; + } + + if (! $this->isName($node->name, 'addUpload')) { + return null; + } + + $args = $node->args; + if (! isset($args[2])) { + return null; + } + + if ($this->valueResolver->isTrue($node->args[2]->value)) { + $node->name = new Identifier('addMultiUpload'); + unset($node->args[2]); + return $node; + } + + return null; + } +} diff --git a/src/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector.php b/src/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector.php new file mode 100644 index 0000000..e0868c4 --- /dev/null +++ b/src/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector.php @@ -0,0 +1,91 @@ +setClass('first'); + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Nette\Utils\Html; + +final class SomeClass +{ + public function run() + { + $html = Html::el(); + $html->appendAttribute('class', 'first'); + } +} +CODE_SAMPLE + + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node->var, new ObjectType('Nette\Utils\Html'))) { + return null; + } + + // @todo posibly extends by more common names + if ($this->isName($node->name, 'setClass')) { + $node->name = new Identifier('appendAttribute'); + + $args = array_merge([new Arg(new String_('class'))], $node->args); + $node->args = $args; + + return $node; + } + + return null; + } +} diff --git a/src/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector.php b/src/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector.php new file mode 100644 index 0000000..22234e7 --- /dev/null +++ b/src/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector.php @@ -0,0 +1,87 @@ +getConfig($defaults) to array_merge', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\DI\CompilerExtension; + +final class SomeExtension extends CompilerExtension +{ + private $defaults = [ + 'key' => 'value' + ]; + + public function loadConfiguration() + { + $config = $this->getConfig($this->defaults); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\DI\CompilerExtension; + +final class SomeExtension extends CompilerExtension +{ + private $defaults = [ + 'key' => 'value' + ]; + + public function loadConfiguration() + { + $config = array_merge($this->defaults, $this->getConfig()); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isOnClassMethodCall($node, new ObjectType('Nette\DI\CompilerExtension'), 'getConfig')) { + return null; + } + + if (count($node->args) !== 1) { + return null; + } + + $getConfigMethodCall = new MethodCall(new Variable('this'), 'getConfig'); + $firstArgValue = $node->args[0]->value; + + return $this->nodeFactory->createFuncCall('array_merge', [$firstArgValue, $getConfigMethodCall]); + } +} diff --git a/src/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector.php b/src/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector.php new file mode 100644 index 0000000..7d8f892 --- /dev/null +++ b/src/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector.php @@ -0,0 +1,86 @@ +getCookie('name', 'default'); + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Nette\Http\Request; + +class SomeClass +{ + public function run(Request $request) + { + return $request->getCookie('name') ?? 'default'; + } +} +CODE_SAMPLE + + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node->var, new ObjectType('Nette\Http\Request'))) { + return null; + } + + if (! $this->isName($node->name, 'getCookie')) { + return null; + } + + // no default value + if (! isset($node->args[1])) { + return null; + } + + $defaultValue = $node->args[1]->value; + unset($node->args[1]); + + return new Coalesce($node, $defaultValue); + } +} diff --git a/src/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector.php b/src/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector.php new file mode 100644 index 0000000..a2875fb --- /dev/null +++ b/src/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector.php @@ -0,0 +1,86 @@ +addDefinition('...') + ->setClass('SomeClass', [1, 2]); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\DI\ContainerBuilder; + +class SomeClass +{ + public function run(ContainerBuilder $containerBuilder) + { + $containerBuilder->addDefinition('...') + ->setFactory('SomeClass', [1, 2]); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isName($node->name, 'setClass')) { + return null; + } + + if (count($node->args) !== 2) { + return null; + } + + if (! $this->isObjectType($node->var, new ObjectType('Nette\DI\Definitions\ServiceDefinition'))) { + return null; + } + + $node->name = new Identifier('setFactory'); + + return $node; + } +} diff --git a/src/Nette/Rector/NotIdentical/StrposToStringsContainsRector.php b/src/Nette/Rector/NotIdentical/StrposToStringsContainsRector.php new file mode 100644 index 0000000..e8a27b6 --- /dev/null +++ b/src/Nette/Rector/NotIdentical/StrposToStringsContainsRector.php @@ -0,0 +1,116 @@ +> + */ + public function getNodeTypes(): array + { + return [NotIdentical::class, Identical::class]; + } + + /** + * @param NotIdentical|Identical $node + */ + public function refactor(Node $node): ?Node + { + $funcCall = $this->matchStrposInComparisonToFalse($node); + if (! $funcCall instanceof FuncCall) { + return null; + } + + if (isset($funcCall->args[2]) && ! $this->valueResolver->isValue($funcCall->args[2]->value, 0)) { + return null; + } + + $containsStaticCall = $this->nodeFactory->createStaticCall('Nette\Utils\Strings', 'contains'); + $containsStaticCall->args[0] = $funcCall->args[0]; + $containsStaticCall->args[1] = $funcCall->args[1]; + + if ($node instanceof Identical) { + return new BooleanNot($containsStaticCall); + } + + return $containsStaticCall; + } + + private function matchStrposInComparisonToFalse(BinaryOp $binaryOp): ?Expr + { + if ($this->valueResolver->isFalse($binaryOp->left)) { + $rightExpr = $binaryOp->right; + if (! $rightExpr instanceof FuncCall) { + return null; + } + + if ($this->isName($rightExpr, 'strpos')) { + return $rightExpr; + } + } + + if ($this->valueResolver->isFalse($binaryOp->right)) { + $leftExpr = $binaryOp->left; + if (! $leftExpr instanceof FuncCall) { + return null; + } + + if ($this->isName($leftExpr, 'strpos')) { + return $leftExpr; + } + } + + return null; + } +} diff --git a/src/Nette/ValueObject/AlwaysTemplateParameterAssign.php b/src/Nette/ValueObject/AlwaysTemplateParameterAssign.php new file mode 100644 index 0000000..f7bca2d --- /dev/null +++ b/src/Nette/ValueObject/AlwaysTemplateParameterAssign.php @@ -0,0 +1,48 @@ +assign = $assign; + $this->parameterName = $parameterName; + $this->assignedExpr = $assignedExpr; + } + + public function getAssign(): Assign + { + return $this->assign; + } + + public function getParameterName(): string + { + return $this->parameterName; + } + + public function getAssignedExpr(): Expr + { + return $this->assignedExpr; + } +} diff --git a/src/Nette/ValueObject/ConditionalTemplateParameterAssign.php b/src/Nette/ValueObject/ConditionalTemplateParameterAssign.php new file mode 100644 index 0000000..2f06414 --- /dev/null +++ b/src/Nette/ValueObject/ConditionalTemplateParameterAssign.php @@ -0,0 +1,36 @@ +assign = $assign; + $this->parameterName = $parameterName; + } + + public function getAssign(): Assign + { + return $this->assign; + } + + public function getParameterName(): string + { + return $this->parameterName; + } +} diff --git a/src/Nette/ValueObject/ContentExprAndNeedleExpr.php b/src/Nette/ValueObject/ContentExprAndNeedleExpr.php new file mode 100644 index 0000000..63e7a7e --- /dev/null +++ b/src/Nette/ValueObject/ContentExprAndNeedleExpr.php @@ -0,0 +1,36 @@ +contentExpr = $contentExpr; + $this->needleExpr = $needleExpr; + } + + public function getContentExpr(): Expr + { + return $this->contentExpr; + } + + public function getNeedleExpr(): Expr + { + return $this->needleExpr; + } +} diff --git a/src/Nette/ValueObject/TemplateParametersAssigns.php b/src/Nette/ValueObject/TemplateParametersAssigns.php new file mode 100644 index 0000000..59ea093 --- /dev/null +++ b/src/Nette/ValueObject/TemplateParametersAssigns.php @@ -0,0 +1,73 @@ +templateParameterAssigns = $alwaysTemplateParameterAssigns; + $this->conditionalTemplateParameterAssign = $conditionalTemplateParameterAssigns; + } + + /** + * @return ConditionalTemplateParameterAssign[] + */ + public function getConditionalTemplateParameterAssign(): array + { + return $this->conditionalTemplateParameterAssign; + } + + /** + * @return string[] + */ + public function getConditionalVariableNames(): array + { + $conditionalVariableNames = []; + foreach ($this->conditionalTemplateParameterAssign as $conditionalTemplateParameterAssign) { + $conditionalVariableNames[] = $conditionalTemplateParameterAssign->getParameterName(); + } + + return array_unique($conditionalVariableNames); + } + + /** + * @return AlwaysTemplateParameterAssign[] + */ + public function getTemplateParameterAssigns(): array + { + return $this->templateParameterAssigns; + } + + /** + * @return array + */ + public function getTemplateVariables(): array + { + $templateVariables = []; + foreach ($this->templateParameterAssigns as $templateParameterAssign) { + $templateVariables[$templateParameterAssign->getParameterName()] = $templateParameterAssign->getAssignedExpr(); + } + + return $templateVariables; + } +} diff --git a/src/NetteCodeQuality/Contract/FormControlTypeResolverInterface.php b/src/NetteCodeQuality/Contract/FormControlTypeResolverInterface.php new file mode 100644 index 0000000..e81edac --- /dev/null +++ b/src/NetteCodeQuality/Contract/FormControlTypeResolverInterface.php @@ -0,0 +1,15 @@ + + */ + public function resolve(Node $node): array; +} diff --git a/src/NetteCodeQuality/Contract/MethodNamesByInputNamesResolverAwareInterface.php b/src/NetteCodeQuality/Contract/MethodNamesByInputNamesResolverAwareInterface.php new file mode 100644 index 0000000..e90af98 --- /dev/null +++ b/src/NetteCodeQuality/Contract/MethodNamesByInputNamesResolverAwareInterface.php @@ -0,0 +1,12 @@ +controlDimFetchAnalyzer = $controlDimFetchAnalyzer; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->netteControlNaming = $netteControlNaming; + $this->returnTypeInferer = $returnTypeInferer; + $this->nodeRepository = $nodeRepository; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof ArrayDimFetch) { + return []; + } + + $controlShortName = $this->controlDimFetchAnalyzer->matchName($node); + if ($controlShortName === null) { + return []; + } + + $createComponentClassMethod = $this->matchCreateComponentClassMethod($node, $controlShortName); + if (! $createComponentClassMethod instanceof ClassMethod) { + return []; + } + + $createComponentClassMethodReturnType = $this->returnTypeInferer->inferFunctionLike( + $createComponentClassMethod + ); + + if (! $createComponentClassMethodReturnType instanceof TypeWithClassName) { + return []; + } + + return [ + $controlShortName => $createComponentClassMethodReturnType->getClassName(), + ]; + } + + private function matchCreateComponentClassMethod( + ArrayDimFetch $arrayDimFetch, + string $controlShortName + ): ?ClassMethod { + $callerType = $this->nodeTypeResolver->getStaticType($arrayDimFetch->var); + if (! $callerType instanceof TypeWithClassName) { + return null; + } + + $createComponentClassMethodName = $this->netteControlNaming->createCreateComponentClassMethodName( + $controlShortName + ); + + return $this->nodeRepository->findClassMethod( + $callerType->getClassName(), + $createComponentClassMethodName + ); + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/AssignDimFetchFormTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/AssignDimFetchFormTypeResolver.php new file mode 100644 index 0000000..5d33dab --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/AssignDimFetchFormTypeResolver.php @@ -0,0 +1,64 @@ +betterNodeFinder = $betterNodeFinder; + $this->nodeTypeResolver = $nodeTypeResolver; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof ArrayDimFetch) { + return []; + } + + // traverse up and find all $this['some_name'] = $type + /** @var Assign|null $formVariableAssign */ + $formVariableAssign = $this->betterNodeFinder->findPreviousAssignToExpr($node); + if (! $formVariableAssign instanceof Assign) { + return []; + } + + if (! $node->dim instanceof String_) { + return []; + } + + $exprType = $this->nodeTypeResolver->getStaticType($formVariableAssign->expr); + if (! $exprType instanceof TypeWithClassName) { + return []; + } + + $name = $node->dim->value; + return [ + $name => $exprType->getClassName(), + ]; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/AssignedVariablesMethodCallsFormTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/AssignedVariablesMethodCallsFormTypeResolver.php new file mode 100644 index 0000000..9115838 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/AssignedVariablesMethodCallsFormTypeResolver.php @@ -0,0 +1,53 @@ +betterNodeFinder = $betterNodeFinder; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof Variable) { + return []; + } + + $formVariableAssign = $this->betterNodeFinder->findPreviousAssignToExpr($node); + if (! $formVariableAssign instanceof Assign) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($formVariableAssign->expr); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/ClassMethodFormTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/ClassMethodFormTypeResolver.php new file mode 100644 index 0000000..02fc7fd --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/ClassMethodFormTypeResolver.php @@ -0,0 +1,70 @@ +betterNodeFinder = $betterNodeFinder; + $this->nodeNameResolver = $nodeNameResolver; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof ClassMethod) { + return []; + } + + if ($this->nodeNameResolver->isName($node, MethodName::CONSTRUCT)) { + return []; + } + + $lastReturn = $this->betterNodeFinder->findLastInstanceOf((array) $node->stmts, Return_::class); + if (! $lastReturn instanceof Return_) { + return []; + } + + if (! $lastReturn->expr instanceof Variable) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($lastReturn->expr); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/ConstructorFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/ConstructorFormControlTypeResolver.php new file mode 100644 index 0000000..ba2e1a8 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/ConstructorFormControlTypeResolver.php @@ -0,0 +1,65 @@ +betterNodeFinder = $betterNodeFinder; + $this->nodeNameResolver = $nodeNameResolver; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof ClassMethod) { + return []; + } + + if (! $this->nodeNameResolver->isName($node, MethodName::CONSTRUCT)) { + return []; + } + + $thisVariable = $this->betterNodeFinder->findVariableOfName($node, 'this'); + if (! $thisVariable instanceof Variable) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($thisVariable); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/GetComponentMethodCallFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/GetComponentMethodCallFormControlTypeResolver.php new file mode 100644 index 0000000..5fe8e73 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/GetComponentMethodCallFormControlTypeResolver.php @@ -0,0 +1,121 @@ +valueResolver = $valueResolver; + $this->nodeNameResolver = $nodeNameResolver; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->nodeRepository = $nodeRepository; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof MethodCall) { + return []; + } + + if (! $this->nodeNameResolver->isName($node->name, 'getComponent')) { + return []; + } + + $createComponentClassMethodName = $this->createCreateComponentMethodName($node); + + $staticType = $this->nodeTypeResolver->getStaticType($node); + + if (! $staticType instanceof FullyQualifiedObjectType) { + return []; + } + + // combine constructor + method body name + $constructorClassMethodData = []; + $constructorClassMethod = $this->nodeRepository->findClassMethod( + $staticType->getClassName(), + MethodName::CONSTRUCT + ); + + if ($constructorClassMethod !== null) { + $constructorClassMethodData = $this->methodNamesByInputNamesResolver->resolveExpr($constructorClassMethod); + } + + $callerType = $this->nodeTypeResolver->getStaticType($node->var); + + $createComponentClassMethodData = []; + if ($callerType instanceof TypeWithClassName) { + $createComponentClassMethod = $this->nodeRepository->findClassMethod( + $callerType->getClassName(), + $createComponentClassMethodName + ); + + if ($createComponentClassMethod !== null) { + $createComponentClassMethodData = $this->methodNamesByInputNamesResolver->resolveExpr( + $createComponentClassMethod + ); + } + } + + return array_merge($constructorClassMethodData, $createComponentClassMethodData); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } + + private function createCreateComponentMethodName(MethodCall $methodCall): string + { + $firstArgumentValue = $methodCall->args[0]->value; + + return 'createComponent' . ucfirst($this->valueResolver->getValue($firstArgumentValue)); + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/MagicNetteFactoryInterfaceFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/MagicNetteFactoryInterfaceFormControlTypeResolver.php new file mode 100644 index 0000000..9f54d87 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/MagicNetteFactoryInterfaceFormControlTypeResolver.php @@ -0,0 +1,170 @@ +nodeTypeResolver = $nodeTypeResolver; + $this->nodeNameResolver = $nodeNameResolver; + $this->nodeRepository = $nodeRepository; + $this->functionLikeParser = $functionLikeParser; + $this->reflectionProvider = $reflectionProvider; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof MethodCall) { + return []; + } + + // skip constructor, handled elsewhere + if ($this->nodeNameResolver->isName($node->name, MethodName::CONSTRUCT)) { + return []; + } + + $methodName = $this->nodeNameResolver->getName($node->name); + if ($methodName === null) { + return []; + } + + $classMethod = $this->nodeRepository->findClassMethodByMethodCall($node); + if (! $classMethod instanceof ClassMethod) { + $classMethod = $this->resolveReflectionClassMethod($node, $methodName); + if (! $classMethod instanceof ClassMethod) { + return []; + } + } + + $classReflection = $this->resolveClassReflectionByMethodCall($node); + if (! $classReflection instanceof ClassReflection) { + return []; + } + + $returnedType = $this->nodeTypeResolver->getStaticType($node); + if (! $returnedType instanceof TypeWithClassName) { + return []; + } + + $constructorClassMethod = $this->nodeRepository->findClassMethod( + $returnedType->getClassName(), + MethodName::CONSTRUCT + ); + + if (! $constructorClassMethod instanceof ClassMethod) { + $constructorClassMethod = $this->resolveReflectionClassMethodFromClassNameAndMethod( + $returnedType->getClassName(), + MethodName::CONSTRUCT + ); + if (! $classMethod instanceof ClassMethod) { + return []; + } + } + + if (! $constructorClassMethod instanceof ClassMethod) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($constructorClassMethod); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } + + private function resolveReflectionClassMethod(MethodCall $methodCall, string $methodName): ?ClassMethod + { + $classReflection = $this->resolveClassReflectionByMethodCall($methodCall); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + $methodReflection = $classReflection->getNativeMethod($methodName); + + return $this->functionLikeParser->parseMethodReflection($methodReflection); + } + + private function resolveReflectionClassMethodFromClassNameAndMethod( + string $className, + string $methodName + ): ?ClassMethod { + if (! $this->reflectionProvider->hasClass($className)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($className); + $methodReflection = $classReflection->getNativeMethod($methodName); + return $this->functionLikeParser->parseMethodReflection($methodReflection); + } + + private function resolveClassReflectionByMethodCall(MethodCall $methodCall): ?ClassReflection + { + $callerClassName = $this->nodeRepository->resolveCallerClassName($methodCall); + if ($callerClassName === null) { + return null; + } + + if (! $this->reflectionProvider->hasClass($callerClassName)) { + return null; + } + + return $this->reflectionProvider->getClass($callerClassName); + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/MethodCallFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/MethodCallFormControlTypeResolver.php new file mode 100644 index 0000000..04507a8 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/MethodCallFormControlTypeResolver.php @@ -0,0 +1,64 @@ +nodeNameResolver = $nodeNameResolver; + $this->nodeRepository = $nodeRepository; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof MethodCall) { + return []; + } + + if ($this->nodeNameResolver->isName($node->name, 'getComponent')) { + return []; + } + + $classMethod = $this->nodeRepository->findClassMethodByMethodCall($node); + if (! $classMethod instanceof ClassMethod) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($classMethod); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/NewFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/NewFormControlTypeResolver.php new file mode 100644 index 0000000..e811608 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/NewFormControlTypeResolver.php @@ -0,0 +1,66 @@ +nodeNameResolver = $nodeNameResolver; + $this->nodeRepository = $nodeRepository; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof New_) { + return []; + } + + $className = $this->nodeNameResolver->getName($node->class); + if ($className === null) { + return []; + } + + $constructorClassMethod = $this->nodeRepository->findClassMethod($className, MethodName::CONSTRUCT); + if (! $constructorClassMethod instanceof ClassMethod) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($constructorClassMethod); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/OnVariableMethodCallsFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/OnVariableMethodCallsFormControlTypeResolver.php new file mode 100644 index 0000000..b7668cf --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/OnVariableMethodCallsFormControlTypeResolver.php @@ -0,0 +1,79 @@ +methodCallManipulator = $methodCallManipulator; + $this->nodeNameResolver = $nodeNameResolver; + $this->valueResolver = $valueResolver; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof Variable) { + return []; + } + + $onFormMethodCalls = $this->methodCallManipulator->findMethodCallsOnVariable($node); + + $methodNamesByInputNames = []; + foreach ($onFormMethodCalls as $onFormMethodCall) { + $methodName = $this->nodeNameResolver->getName($onFormMethodCall->name); + if ($methodName === null) { + continue; + } + + if (! isset(NetteFormMethodNameToControlType::METHOD_NAME_TO_CONTROL_TYPE[$methodName])) { + continue; + } + + if (! isset($onFormMethodCall->args[0])) { + continue; + } + + $addedInputName = $this->valueResolver->getValue($onFormMethodCall->args[0]->value); + if (! is_string($addedInputName)) { + throw new ShouldNotHappenException(); + } + + $methodNamesByInputNames[$addedInputName] = $methodName; + } + + return $methodNamesByInputNames; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/ReturnFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/ReturnFormControlTypeResolver.php new file mode 100644 index 0000000..03d9682 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/ReturnFormControlTypeResolver.php @@ -0,0 +1,58 @@ +betterNodeFinder = $betterNodeFinder; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof Return_) { + return []; + } + + if (! $node->expr instanceof Variable) { + return []; + } + + $initialAssign = $this->betterNodeFinder->findPreviousAssignToExpr($node->expr); + if (! $initialAssign instanceof Assign) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($node); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/ThisVariableInAnotherMethodFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/ThisVariableInAnotherMethodFormControlTypeResolver.php new file mode 100644 index 0000000..f7c51f2 --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/ThisVariableInAnotherMethodFormControlTypeResolver.php @@ -0,0 +1,57 @@ + + */ + public function resolve(Node $node): array + { + if (! $node instanceof Variable) { + return []; + } + + $methodName = $node->getAttribute(AttributeKey::METHOD_NAME); + + // handled elsewhere + if ($methodName === MethodName::CONSTRUCT) { + return []; + } + + $classLike = $node->getAttribute(AttributeKey::CLASS_NODE); + if (! $classLike instanceof Class_) { + return []; + } + + $constructClassMethod = $classLike->getMethod(MethodName::CONSTRUCT); + if (! $constructClassMethod instanceof ClassMethod) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($constructClassMethod); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/FormControlTypeResolver/VariableConstructorFormControlTypeResolver.php b/src/NetteCodeQuality/FormControlTypeResolver/VariableConstructorFormControlTypeResolver.php new file mode 100644 index 0000000..8b4c62c --- /dev/null +++ b/src/NetteCodeQuality/FormControlTypeResolver/VariableConstructorFormControlTypeResolver.php @@ -0,0 +1,99 @@ +nodeTypeResolver = $nodeTypeResolver; + $this->nodeNameResolver = $nodeNameResolver; + $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; + } + + /** + * @return array + */ + public function resolve(Node $node): array + { + if (! $node instanceof Variable) { + return []; + } + + // handled else-where + if ($this->nodeNameResolver->isName($node, 'this')) { + return []; + } + + $formType = $this->nodeTypeResolver->getStaticType($node); + if (! $formType instanceof TypeWithClassName) { + return []; + } + + $formClassReflection = $this->reflectionProvider->getClass($formType->getClassName()); + + if (! $formClassReflection->isSubclassOf('Nette\Application\UI\Form')) { + return []; + } + + $constructorClassMethod = $this->nodeRepository->findClassMethod( + $formType->getClassName(), + MethodName::CONSTRUCT + ); + if (! $constructorClassMethod instanceof ClassMethod) { + return []; + } + + return $this->methodNamesByInputNamesResolver->resolveExpr($constructorClassMethod); + } + + public function setResolver(MethodNamesByInputNamesResolver $methodNamesByInputNamesResolver): void + { + $this->methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } +} diff --git a/src/NetteCodeQuality/Naming/NetteControlNaming.php b/src/NetteCodeQuality/Naming/NetteControlNaming.php new file mode 100644 index 0000000..6aaea0b --- /dev/null +++ b/src/NetteCodeQuality/Naming/NetteControlNaming.php @@ -0,0 +1,31 @@ +camelize(); + + if (Strings::endsWith($variableName, 'Form')) { + return $variableName; + } + + return $variableName . 'Control'; + } + + public function createCreateComponentClassMethodName(string $shortName): string + { + $stringy = new Stringy($shortName); + $componentName = (string) $stringy->upperCamelize(); + + return 'createComponent' . $componentName; + } +} diff --git a/src/NetteCodeQuality/NodeAdding/FunctionLikeFirstLevelStatementResolver.php b/src/NetteCodeQuality/NodeAdding/FunctionLikeFirstLevelStatementResolver.php new file mode 100644 index 0000000..53d5913 --- /dev/null +++ b/src/NetteCodeQuality/NodeAdding/FunctionLikeFirstLevelStatementResolver.php @@ -0,0 +1,84 @@ +parentScopeFinder = $parentScopeFinder; + $this->betterNodeFinder = $betterNodeFinder; + } + + public function resolveFirstLevelStatement(Node $node): Node + { + $multiplierClosure = $this->matchMultiplierClosure($node); + /** @var ClassMethod|Closure|null $functionLike */ + $functionLike = $multiplierClosure ?? $this->parentScopeFinder->find($node); + + if ($functionLike === null) { + throw new ShouldNotHappenException(); + } + + $currentStatement = $node->getAttribute(AttributeKey::CURRENT_STATEMENT); + if (! $currentStatement instanceof Node) { + throw new ShouldNotHappenException(); + } + + while (! in_array($currentStatement, (array) $functionLike->stmts, true)) { + $parent = $currentStatement->getAttribute(AttributeKey::PARENT_NODE); + if (! $parent instanceof Node) { + throw new ShouldNotHappenException(); + } + + $currentStatement = $parent->getAttribute(AttributeKey::CURRENT_STATEMENT); + } + + return $currentStatement; + } + + /** + * Form might be costructured inside private closure for multiplier + * @see https://doc.nette.org/en/3.0/multiplier + */ + private function matchMultiplierClosure(Node $node): ?Closure + { + $closure = $this->betterNodeFinder->findParentType($node, Closure::class); + if (! $closure instanceof Closure) { + return null; + } + + $parent = $closure->getAttribute(AttributeKey::PARENT_NODE); + if (! $parent instanceof Arg) { + return null; + } + + $parentParent = $parent->getAttribute(AttributeKey::PARENT_NODE); + if (! $parentParent instanceof New_) { + return null; + } + + return $closure; + } +} diff --git a/src/NetteCodeQuality/NodeAnalyzer/ArrayDimFetchAnalyzer.php b/src/NetteCodeQuality/NodeAnalyzer/ArrayDimFetchAnalyzer.php new file mode 100644 index 0000000..f30aa68 --- /dev/null +++ b/src/NetteCodeQuality/NodeAnalyzer/ArrayDimFetchAnalyzer.php @@ -0,0 +1,26 @@ +getAttribute(AttributeKey::PARENT_NODE); + if (! $parent instanceof Assign) { + return false; + } + + if ($parent->var === $arrayDimFetch) { + return true; + } + + return $parent->expr === $arrayDimFetch; + } +} diff --git a/src/NetteCodeQuality/NodeAnalyzer/AssignAnalyzer.php b/src/NetteCodeQuality/NodeAnalyzer/AssignAnalyzer.php new file mode 100644 index 0000000..4186b2f --- /dev/null +++ b/src/NetteCodeQuality/NodeAnalyzer/AssignAnalyzer.php @@ -0,0 +1,109 @@ +functionLikeFirstLevelStatementResolver = $functionLikeFirstLevelStatementResolver; + $this->nodesToAddCollector = $nodesToAddCollector; + $this->varAnnotationManipulator = $varAnnotationManipulator; + } + + public function addAssignExpressionForFirstCase( + string $variableName, + ArrayDimFetch $arrayDimFetch, + ObjectType $controlObjectType + ): void { + if ($this->shouldSkipForAlreadyAddedInCurrentClassMethod($arrayDimFetch, $variableName)) { + return; + } + + $assignExpression = $this->createAnnotatedAssignExpression($variableName, $arrayDimFetch, $controlObjectType); + + $currentStatement = $this->functionLikeFirstLevelStatementResolver->resolveFirstLevelStatement($arrayDimFetch); + $this->nodesToAddCollector->addNodeBeforeNode($assignExpression, $currentStatement); + } + + private function shouldSkipForAlreadyAddedInCurrentClassMethod( + ArrayDimFetch $arrayDimFetch, + string $variableName + ): bool { + $classMethod = $arrayDimFetch->getAttribute(AttributeKey::METHOD_NODE); + + if (! $classMethod instanceof ClassMethod) { + return false; + } + + $classMethodObjectHash = spl_object_hash($classMethod) . $variableName; + if (in_array($classMethodObjectHash, $this->alreadyInitializedAssignsClassMethodObjectHashes, true)) { + return true; + } + + $this->alreadyInitializedAssignsClassMethodObjectHashes[] = $classMethodObjectHash; + + return false; + } + + private function createAnnotatedAssignExpression( + string $variableName, + ArrayDimFetch $arrayDimFetch, + ObjectType $controlObjectType + ): Expression { + $assignExpression = $this->createAssignExpression($variableName, $arrayDimFetch); + + $this->varAnnotationManipulator->decorateNodeWithInlineVarType( + $assignExpression, + $controlObjectType, + $variableName + ); + + return $assignExpression; + } + + private function createAssignExpression(string $variableName, ArrayDimFetch $arrayDimFetch): Expression + { + $variable = new Variable($variableName); + $assignedArrayDimFetch = clone $arrayDimFetch; + $assign = new Assign($variable, $assignedArrayDimFetch); + + return new Expression($assign); + } +} diff --git a/src/NetteCodeQuality/NodeAnalyzer/BinaryOpAnalyzer.php b/src/NetteCodeQuality/NodeAnalyzer/BinaryOpAnalyzer.php new file mode 100644 index 0000000..dad3e7a --- /dev/null +++ b/src/NetteCodeQuality/NodeAnalyzer/BinaryOpAnalyzer.php @@ -0,0 +1,44 @@ +nodeNameResolver = $nodeNameResolver; + } + + public function matchFuncCallAndOtherExpr(BinaryOp $binaryOp, string $funcCallName): ?FuncCallAndExpr + { + if ($binaryOp->left instanceof FuncCall) { + if (! $this->nodeNameResolver->isName($binaryOp->left, $funcCallName)) { + return null; + } + + return new FuncCallAndExpr($binaryOp->left, $binaryOp->right); + } + + if ($binaryOp->right instanceof FuncCall) { + if (! $this->nodeNameResolver->isName($binaryOp->right, $funcCallName)) { + return null; + } + + return new FuncCallAndExpr($binaryOp->right, $binaryOp->left); + } + + return null; + } +} diff --git a/src/NetteCodeQuality/NodeAnalyzer/ControlDimFetchAnalyzer.php b/src/NetteCodeQuality/NodeAnalyzer/ControlDimFetchAnalyzer.php new file mode 100644 index 0000000..774c8eb --- /dev/null +++ b/src/NetteCodeQuality/NodeAnalyzer/ControlDimFetchAnalyzer.php @@ -0,0 +1,86 @@ +nodeTypeResolver = $nodeTypeResolver; + } + + public function matchNameOnFormOrControlVariable(Node $node): ?string + { + return $this->matchNameOnVariableType($node, new ObjectType('Nette\Application\UI\Form')); + } + + public function matchNameOnControlVariable(Node $node): ?string + { + return $this->matchNameOnVariableType($node, new ObjectType('Nette\Application\UI\Control')); + } + + public function matchName(Node $node): ?string + { + if (! $node instanceof ArrayDimFetch) { + return null; + } + + if (! $this->isVariableTypes($node->var, [new ObjectType('Nette\ComponentModel\IContainer')])) { + return null; + } + + if (! $node->dim instanceof String_) { + return null; + } + + return $node->dim->value; + } + + private function matchNameOnVariableType(Node $node, ObjectType $objectType): ?string + { + $matchedName = $this->matchName($node); + if ($matchedName === null) { + return null; + } + + /** @var Assign $node */ + if (! $this->isVariableTypes($node->var, [$objectType])) { + return null; + } + + return $matchedName; + } + + /** + * @param ObjectType[] $objectTypes + */ + private function isVariableTypes(Node $node, array $objectTypes): bool + { + if (! $node instanceof Variable) { + return false; + } + + foreach ($objectTypes as $objectType) { + if ($this->nodeTypeResolver->isObjectType($node, $objectType)) { + return true; + } + } + + return false; + } +} diff --git a/src/NetteCodeQuality/NodeResolver/FormVariableInputNameTypeResolver.php b/src/NetteCodeQuality/NodeResolver/FormVariableInputNameTypeResolver.php new file mode 100644 index 0000000..4a033ae --- /dev/null +++ b/src/NetteCodeQuality/NodeResolver/FormVariableInputNameTypeResolver.php @@ -0,0 +1,44 @@ +methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + } + + public function resolveControlTypeByInputName(Expr $formOrControlExpr, string $inputName): string + { + $methodNamesByInputNames = $this->methodNamesByInputNamesResolver->resolveExpr($formOrControlExpr); + + $formAddMethodName = $methodNamesByInputNames[$inputName] ?? null; + if ($formAddMethodName === null) { + $message = sprintf('Type was not found for "%s" input name', $inputName); + throw new ShouldNotHappenException($message); + } + + foreach (NetteFormMethodNameToControlType::METHOD_NAME_TO_CONTROL_TYPE as $methodName => $controlType) { + if ($methodName !== $formAddMethodName) { + continue; + } + + return $controlType; + } + + throw new NotImplementedYetException($formAddMethodName); + } +} diff --git a/src/NetteCodeQuality/NodeResolver/MethodNamesByInputNamesResolver.php b/src/NetteCodeQuality/NodeResolver/MethodNamesByInputNamesResolver.php new file mode 100644 index 0000000..c8e4575 --- /dev/null +++ b/src/NetteCodeQuality/NodeResolver/MethodNamesByInputNamesResolver.php @@ -0,0 +1,46 @@ +setResolver($this); + } + + $this->formControlTypeResolvers[] = $formControlTypeResolver; + } + } + + /** + * @return array + */ + public function resolveExpr(Node $node): array + { + $methodNamesByInputNames = []; + + foreach ($this->formControlTypeResolvers as $formControlTypeResolver) { + $currentMethodNamesByInputNames = $formControlTypeResolver->resolve($node); + $methodNamesByInputNames = array_merge($methodNamesByInputNames, $currentMethodNamesByInputNames); + } + + return $methodNamesByInputNames; + } +} diff --git a/src/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector.php b/src/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector.php new file mode 100644 index 0000000..3ecaf8e --- /dev/null +++ b/src/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector.php @@ -0,0 +1,195 @@ +methodNamesByInputNamesResolver = $methodNamesByInputNamesResolver; + $this->arrayDimFetchRenamer = $arrayDimFetchRenamer; + $this->arrayDimFetchAnalyzer = $arrayDimFetchAnalyzer; + $this->controlDimFetchAnalyzer = $controlDimFetchAnalyzer; + $this->netteControlNaming = $netteControlNaming; + $this->assignAnalyzer = $assignAnalyzer; + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ArrayDimFetch::class]; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change magic $this["some_component"] to variable assign with @var annotation', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Presenter; +use Nette\Application\UI\Form; + +final class SomePresenter extends Presenter +{ + public function run() + { + if ($this['some_form']->isSubmitted()) { + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\Application\UI\Presenter; +use Nette\Application\UI\Form; + +final class SomePresenter extends Presenter +{ + public function run() + { + /** @var \Nette\Application\UI\Form $someForm */ + $someForm = $this['some_form']; + if ($someForm->isSubmitted()) { + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @param ArrayDimFetch $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkip($node)) { + return null; + } + + $controlName = $this->controlDimFetchAnalyzer->matchNameOnControlVariable($node); + if ($controlName === null) { + return null; + } + + // probably multiplier factory, nothing we can do... yet + if (Strings::contains($controlName, '-')) { + return null; + } + + $variableName = $this->netteControlNaming->createVariableName($controlName); + $controlObjectType = $this->resolveControlType($node, $controlName); + $this->assignAnalyzer->addAssignExpressionForFirstCase($variableName, $node, $controlObjectType); + + $classMethod = $node->getAttribute(AttributeKey::METHOD_NODE); + if ($classMethod instanceof ClassMethod) { + $this->arrayDimFetchRenamer->renameToVariable($classMethod, $node, $variableName); + } + + return new Variable($variableName); + } + + private function shouldSkip(ArrayDimFetch $arrayDimFetch): bool + { + if ($this->arrayDimFetchAnalyzer->isBeingAssignedOrInitialized($arrayDimFetch)) { + return true; + } + + $parent = $arrayDimFetch->getAttribute(AttributeKey::PARENT_NODE); + if (StaticInstanceOf::isOneOf($parent, [Isset_::class, Unset_::class])) { + return ! $arrayDimFetch->dim instanceof Variable; + } + + return false; + } + + private function resolveControlType(ArrayDimFetch $arrayDimFetch, string $controlName): ObjectType + { + $controlTypes = $this->methodNamesByInputNamesResolver->resolveExpr($arrayDimFetch); + if ($controlTypes === []) { + throw new NotImplementedYetException($controlName); + } + + if (! isset($controlTypes[$controlName])) { + throw new ShouldNotHappenException($controlName); + } + + return new ObjectType($controlTypes[$controlName]); + } +} diff --git a/src/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector.php b/src/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector.php new file mode 100644 index 0000000..97905ec --- /dev/null +++ b/src/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector.php @@ -0,0 +1,161 @@ +formVariableInputNameTypeResolver = $formVariableInputNameTypeResolver; + $this->typeChecker = $typeChecker; + $this->arrayDimFetchAnalyzer = $arrayDimFetchAnalyzer; + $this->netteControlNaming = $netteControlNaming; + $this->assignAnalyzer = $assignAnalyzer; + $this->controlDimFetchAnalyzer = $controlDimFetchAnalyzer; + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ArrayDimFetch::class]; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change array access magic on $form to explicit standalone typed variable', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Form; + +class SomePresenter +{ + public function run() + { + $form = new Form(); + $this->addText('email', 'Email'); + + $form['email']->value = 'hey@hi.hello'; + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\Application\UI\Form; + +class SomePresenter +{ + public function run() + { + $form = new Form(); + $this->addText('email', 'Email'); + + /** @var \Nette\Forms\Controls\TextInput $emailControl */ + $emailControl = $form['email']; + $emailControl->value = 'hey@hi.hello'; + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @param ArrayDimFetch $node + */ + public function refactor(Node $node): ?Node + { + if ($this->arrayDimFetchAnalyzer->isBeingAssignedOrInitialized($node)) { + return null; + } + + $parent = $node->getAttribute(AttributeKey::PARENT_NODE); + if ($this->typeChecker->isInstanceOf($parent, [Isset_::class, Unset_::class])) { + return null; + } + + $inputName = $this->controlDimFetchAnalyzer->matchNameOnFormOrControlVariable($node); + if ($inputName === null) { + return null; + } + + $formVariableName = $this->getName($node->var); + if ($formVariableName === null) { + throw new ShouldNotHappenException(); + } + + // 1. find previous calls on variable + $controlType = $this->formVariableInputNameTypeResolver->resolveControlTypeByInputName( + $node->var, + $inputName + ); + + $controlVariableName = $this->netteControlNaming->createVariableName($inputName); + $controlObjectType = new ObjectType($controlType); + $this->assignAnalyzer->addAssignExpressionForFirstCase($controlVariableName, $node, $controlObjectType); + + return new Variable($controlVariableName); + } +} diff --git a/src/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector.php b/src/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector.php new file mode 100644 index 0000000..4f1ab47 --- /dev/null +++ b/src/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector.php @@ -0,0 +1,92 @@ +getComponent(...) method', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Presenter; + +class SomeClass extends Presenter +{ + public function some() + { + $someControl = $this['whatever']; + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\Application\UI\Presenter; + +class SomeClass extends Presenter +{ + public function some() + { + $someControl = $this->getComponent('whatever'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Assign::class]; + } + + /** + * @param Assign $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isFetchOfControlFromPresenterDimFetch($node)) { + return null; + } + + /** @var ArrayDimFetch $arrayDimFetch */ + $arrayDimFetch = $node->expr; + + $args = $this->nodeFactory->createArgs([$arrayDimFetch->dim]); + $node->expr = new MethodCall($arrayDimFetch->var, 'getComponent', $args); + + return $node; + } + + private function isFetchOfControlFromPresenterDimFetch(Assign $assign): bool + { + if (! $assign->expr instanceof ArrayDimFetch) { + return false; + } + + $arrayDimFetch = $assign->expr; + + return $this->isObjectType($arrayDimFetch->var, new ObjectType('Nette\Application\UI\Presenter')); + } +} diff --git a/src/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector.php b/src/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector.php new file mode 100644 index 0000000..63059d6 --- /dev/null +++ b/src/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector.php @@ -0,0 +1,105 @@ +setComponent(...) method', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; +use Nette\Application\UI\Presenter; + +class SomeClass extends Presenter +{ + public function some() + { + $someControl = new Control(); + $this['whatever'] = $someControl; + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; +use Nette\Application\UI\Presenter; + +class SomeClass extends Presenter +{ + public function some() + { + $someControl = new Control(); + $this->addComponent($someControl, 'whatever'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Assign::class]; + } + + /** + * @param Assign $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isAssignOfControlToPresenterDimFetch($node)) { + return null; + } + + /** @var ArrayDimFetch $arrayDimFetch */ + $arrayDimFetch = $node->var; + + $arguments = [$node->expr, $arrayDimFetch->dim]; + + $arg = $this->nodeFactory->createArgs($arguments); + + return new MethodCall($arrayDimFetch->var, 'addComponent', $arg); + } + + private function isAssignOfControlToPresenterDimFetch(Assign $assign): bool + { + if (! $assign->var instanceof ArrayDimFetch) { + return false; + } + + if (! $this->isObjectType($assign->expr, new ObjectType('Nette\Application\UI\Control'))) { + return false; + } + + $arrayDimFetch = $assign->var; + if (! $arrayDimFetch->var instanceof Variable) { + return false; + } + + return $this->isObjectType($arrayDimFetch->var, new ObjectType('Nette\Application\UI\Presenter')); + } +} diff --git a/src/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector.php b/src/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector.php new file mode 100644 index 0000000..b540f6c --- /dev/null +++ b/src/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector.php @@ -0,0 +1,246 @@ +varAnnotationManipulator = $varAnnotationManipulator; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Add doc type for magic $control->getComponent(...) assign', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +final class SomeClass +{ + public function run() + { + $externalControl = new ExternalControl(); + $anotherControl = $externalControl->getComponent('another'); + } +} + +final class ExternalControl extends Control +{ + public function createComponentAnother(): AnotherControl + { + return new AnotherControl(); + } +} + +final class AnotherControl extends Control +{ +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +final class SomeClass +{ + public function run() + { + $externalControl = new ExternalControl(); + /** @var AnotherControl $anotherControl */ + $anotherControl = $externalControl->getComponent('another'); + } +} + +final class ExternalControl extends Control +{ + public function createComponentAnother(): AnotherControl + { + return new AnotherControl(); + } +} + +final class AnotherControl extends Control +{ +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Assign::class]; + } + + /** + * @param Assign $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isGetComponentMethodCallOrArrayDimFetchOnControl($node->expr)) { + return null; + } + + if (! $node->var instanceof Variable) { + return null; + } + + $variableName = $this->getName($node->var); + if ($variableName === null) { + return null; + } + + $nodeVar = $this->getObjectType($node->var); + if (! $nodeVar instanceof MixedType) { + return null; + } + + $controlType = $this->resolveControlType($node); + if (! $controlType instanceof TypeWithClassName) { + return null; + } + + $this->varAnnotationManipulator->decorateNodeWithInlineVarType($node, $controlType, $variableName); + + return $node; + } + + private function isGetComponentMethodCallOrArrayDimFetchOnControl(Expr $expr): bool + { + if (! $expr instanceof MethodCall) { + return $this->isArrayDimFetchStringOnControlVariable($expr); + } + if (! $this->isOnClassMethodCall($expr, new ObjectType('Nette\Application\UI\Control'), 'getComponent')) { + return $this->isArrayDimFetchStringOnControlVariable($expr); + } + return true; + } + + private function resolveControlType(Assign $assign): Type + { + if ($assign->expr instanceof MethodCall) { + /** @var MethodCall $methodCall */ + $methodCall = $assign->expr; + return $this->resolveCreateComponentMethodCallReturnType($methodCall); + } + + if ($assign->expr instanceof ArrayDimFetch) { + /** @var ArrayDimFetch $arrayDimFetch */ + $arrayDimFetch = $assign->expr; + return $this->resolveArrayDimFetchControlType($arrayDimFetch); + } + + return new MixedType(); + } + + private function isArrayDimFetchStringOnControlVariable(Expr $expr): bool + { + if (! $expr instanceof ArrayDimFetch) { + return false; + } + + if (! $expr->dim instanceof String_) { + return false; + } + + $varStaticType = $this->getStaticType($expr->var); + if (! $varStaticType instanceof TypeWithClassName) { + return false; + } + + return is_a($varStaticType->getClassName(), 'Nette\Application\UI\Control', true); + } + + private function resolveCreateComponentMethodCallReturnType(MethodCall $methodCall): Type + { + $scope = $methodCall->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return new MixedType(); + } + + if (count($methodCall->args) !== 1) { + return new MixedType(); + } + + $firstArgumentValue = $methodCall->args[0]->value; + if (! $firstArgumentValue instanceof String_) { + return new MixedType(); + } + + return $this->resolveTypeFromShortControlNameAndVariable($firstArgumentValue, $scope, $methodCall->var); + } + + private function resolveArrayDimFetchControlType(ArrayDimFetch $arrayDimFetch): Type + { + $scope = $arrayDimFetch->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + throw new ShouldNotHappenException(); + } + + if (! $arrayDimFetch->dim instanceof String_) { + return new MixedType(); + } + + return $this->resolveTypeFromShortControlNameAndVariable($arrayDimFetch->dim, $scope, $arrayDimFetch->var); + } + + private function resolveTypeFromShortControlNameAndVariable( + String_ $shortControlString, + Scope $scope, + Expr $expr + ): Type { + $componentName = $this->valueResolver->getValue($shortControlString); + $componentName = ucfirst($componentName); + + $methodName = sprintf('createComponent%s', $componentName); + + $calledOnType = $scope->getType($expr); + if (! $calledOnType instanceof TypeWithClassName) { + return new MixedType(); + } + + if (! $calledOnType->hasMethod($methodName)->yes()) { + return new MixedType(); + } + + // has method + $methodReflection = $calledOnType->getMethod($methodName, $scope); + + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } +} diff --git a/src/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector.php b/src/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector.php new file mode 100644 index 0000000..9d505cb --- /dev/null +++ b/src/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector.php @@ -0,0 +1,127 @@ +netteClassAnalyzer = $netteClassAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Change $this->template->setFile() $this->template->render()', [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +final class SomeControl extends Control +{ + public function render() + { + $this->template->setFile(__DIR__ . '/someFile.latte'); + $this->template->render(); + } +} +CODE_SAMPLE + + , + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +final class SomeControl extends Control +{ + public function render() + { + $this->template->render(__DIR__ . '/someFile.latte'); + } +} +CODE_SAMPLE + + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->netteClassAnalyzer->isInComponent($node)) { + return null; + } + + /** @var MethodCall[] $methodCalls */ + $methodCalls = $this->betterNodeFinder->findInstanceOf((array) $node->stmts, MethodCall::class); + + $setFileMethodCall = $this->resolveSingleSetFileMethodCall($methodCalls); + if (! $setFileMethodCall instanceof MethodCall) { + return null; + } + + foreach ($methodCalls as $methodCall) { + if (! $this->isName($methodCall->name, 'render')) { + continue; + } + + if (isset($methodCall->args[0])) { + continue; + } + + $this->removeNode($setFileMethodCall); + $methodCall->args[0] = new Arg($setFileMethodCall->args[0]->value); + return $node; + } + + return null; + } + + /** + * @param MethodCall[] $methodCalls + */ + private function resolveSingleSetFileMethodCall(array $methodCalls): ?MethodCall + { + $singleSetFileMethodCall = null; + foreach ($methodCalls as $methodCall) { + if (! $this->isName($methodCall->name, 'setFile')) { + continue; + } + + if ($singleSetFileMethodCall instanceof MethodCall) { + return null; + } + + $singleSetFileMethodCall = $methodCall; + } + + return $singleSetFileMethodCall; + } +} diff --git a/src/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector.php b/src/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector.php new file mode 100644 index 0000000..f5502c5 --- /dev/null +++ b/src/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector.php @@ -0,0 +1,155 @@ +propertyUsageAnalyzer = $propertyUsageAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Move @inject properties to constructor, if there already is one', + [ + new CodeSample( + <<<'CODE_SAMPLE' +final class SomeClass +{ + /** + * @var SomeDependency + * @inject + */ + public $someDependency; + + /** + * @var OtherDependency + */ + private $otherDependency; + + public function __construct(OtherDependency $otherDependency) + { + $this->otherDependency = $otherDependency; + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +final class SomeClass +{ + /** + * @var SomeDependency + */ + private $someDependency; + + /** + * @var OtherDependency + */ + private $otherDependency; + + public function __construct(OtherDependency $otherDependency, SomeDependency $someDependency) + { + $this->otherDependency = $otherDependency; + $this->someDependency = $someDependency; + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + $injectProperties = $this->getInjectProperties($node); + if ($injectProperties === []) { + return null; + } + + $constructClassMethod = $node->getMethod(MethodName::CONSTRUCT); + if (! $constructClassMethod instanceof ClassMethod) { + return null; + } + + foreach ($injectProperties as $injectProperty) { + $this->removeInjectAnnotation($injectProperty); + $this->changePropertyVisibility($injectProperty); + $this->propertyAdder->addPropertyToCollector($injectProperty); + + if ($this->isAtLeastPhpVersion(PhpVersionFeature::PROPERTY_PROMOTION)) { + $this->removeNode($injectProperty); + } + } + + return $node; + } + + /** + * @return Property[] + */ + private function getInjectProperties(Class_ $class): array + { + return array_filter($class->getProperties(), function (Property $property): bool { + return $this->isInjectProperty($property); + }); + } + + private function removeInjectAnnotation(Property $property): void + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); + $phpDocInfo->removeByType(NetteInjectTagNode::class); + } + + private function changePropertyVisibility(Property $injectProperty): void + { + if ($this->propertyUsageAnalyzer->isPropertyFetchedInChildClass($injectProperty)) { + $this->visibilityManipulator->makeProtected($injectProperty); + } else { + $this->visibilityManipulator->makePrivate($injectProperty); + } + } + + private function isInjectProperty(Property $property): bool + { + if (! $property->isPublic()) { + return false; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); + return $phpDocInfo->hasByType(NetteInjectTagNode::class); + } +} diff --git a/src/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector.php b/src/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector.php new file mode 100644 index 0000000..9b1be49 --- /dev/null +++ b/src/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector.php @@ -0,0 +1,105 @@ +binaryOpAnalyzer = $binaryOpAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change substr function with minus to Strings::endsWith()', + [ + new CodeSample( + <<<'CODE_SAMPLE' +substr($var, -4) !== 'Test'; +substr($var, -4) === 'Test'; +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +! \Nette\Utils\Strings::endsWith($var, 'Test'); +\Nette\Utils\Strings::endsWith($var, 'Test'); +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Identical::class, NotIdentical::class]; + } + + /** + * @param Identical|NotIdentical $node + */ + public function refactor(Node $node): ?Node + { + $funcCallAndExpr = $this->binaryOpAnalyzer->matchFuncCallAndOtherExpr($node, self::SUBSTR); + if (! $funcCallAndExpr instanceof FuncCallAndExpr) { + return null; + } + + $substrFuncCall = $funcCallAndExpr->getFuncCall(); + if (! $substrFuncCall->args[1]->value instanceof UnaryMinus) { + return null; + } + + /** @var UnaryMinus $unaryMinus */ + $unaryMinus = $substrFuncCall->args[1]->value; + if (! $unaryMinus->expr instanceof LNumber) { + return null; + } + + $string = $funcCallAndExpr->getExpr(); + + $wordLength = $unaryMinus->expr->value; + if ($string instanceof String_ && strlen($string->value) !== $wordLength) { + return null; + } + + $arguments = [$substrFuncCall->args[0]->value, $string]; + $staticCall = $this->nodeFactory->createStaticCall(Strings::class, 'endsWith', $arguments); + + if ($node instanceof Identical) { + return $staticCall; + } + + return new BooleanNot($staticCall); + } +} diff --git a/src/NetteCodeQuality/ValueObject/FuncCallAndExpr.php b/src/NetteCodeQuality/ValueObject/FuncCallAndExpr.php new file mode 100644 index 0000000..c0e3fa7 --- /dev/null +++ b/src/NetteCodeQuality/ValueObject/FuncCallAndExpr.php @@ -0,0 +1,37 @@ +funcCall = $funcCall; + $this->expr = $expr; + } + + public function getFuncCall(): FuncCall + { + return $this->funcCall; + } + + public function getExpr(): Expr + { + return $this->expr; + } +} diff --git a/src/NetteCodeQuality/ValueObject/NetteFormMethodNameToControlType.php b/src/NetteCodeQuality/ValueObject/NetteFormMethodNameToControlType.php new file mode 100644 index 0000000..ed88c8d --- /dev/null +++ b/src/NetteCodeQuality/ValueObject/NetteFormMethodNameToControlType.php @@ -0,0 +1,34 @@ +> + */ + public const METHOD_NAME_TO_CONTROL_TYPE = [ + 'addText' => 'Nette\Forms\Controls\TextInput', + 'addPassword' => 'Nette\Forms\Controls\TextInput', + 'addEmail' => 'Nette\Forms\Controls\TextInput', + 'addInteger' => 'Nette\Forms\Controls\TextInput', + 'addUpload' => 'Nette\Forms\Controls\UploadControl', + 'addMultiUpload' => 'Nette\Forms\Controls\UploadControl', + 'addTextArea' => 'Nette\Forms\Controls\TextArea', + 'addHidden' => 'Nette\Forms\Controls\HiddenField', + 'addCheckbox' => 'Nette\Forms\Controls\Checkbox', + 'addRadioList' => 'Nette\Forms\Controls\RadioList', + 'addCheckboxList' => 'Nette\Forms\Controls\CheckboxList', + 'addSelect' => 'Nette\Forms\Controls\SelectBox', + 'addMultiSelect' => 'Nette\Forms\Controls\MultiSelectBox', + 'addSubmit' => 'Nette\Forms\Controls\SubmitButton', + 'addButton' => 'Nette\Forms\Controls\Button', + 'addImage' => 'Nette\Forms\Controls\ImageButton', + // custom + 'addJSelect' => 'DependentSelectBox\JsonDependentSelectBox', + ]; +} diff --git a/src/NetteKdyby/BlueprintFactory/VariableWithTypesFactory.php b/src/NetteKdyby/BlueprintFactory/VariableWithTypesFactory.php new file mode 100644 index 0000000..3077b2c --- /dev/null +++ b/src/NetteKdyby/BlueprintFactory/VariableWithTypesFactory.php @@ -0,0 +1,70 @@ +variableNaming = $variableNaming; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->staticTypeMapper = $staticTypeMapper; + } + + /** + * @param Arg[] $args + * @return VariableWithType[] + */ + public function createVariablesWithTypesFromArgs(array $args): array + { + $variablesWithTypes = []; + + foreach ($args as $arg) { + $staticType = $this->nodeTypeResolver->getStaticType($arg->value); + $variableName = $this->variableNaming->resolveFromNodeAndType($arg, $staticType); + if ($variableName === null) { + throw new ShouldNotHappenException(); + } + + // compensate for static + if ($staticType instanceof StaticType) { + $staticType = new ObjectType($staticType->getClassName()); + } + + $phpParserTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($staticType); + + $variablesWithTypes[] = new VariableWithType($variableName, $staticType, $phpParserTypeNode); + } + + return $variablesWithTypes; + } +} diff --git a/src/NetteKdyby/ContributeEventClassResolver.php b/src/NetteKdyby/ContributeEventClassResolver.php new file mode 100644 index 0000000..cb5dd8e --- /dev/null +++ b/src/NetteKdyby/ContributeEventClassResolver.php @@ -0,0 +1,182 @@ +> + */ + private const CONTRIBUTTE_EVENT_GETTER_METHODS_WITH_TYPE = [ + // application + 'Contributte\Events\Extra\Event\Application\ShutdownEvent' => [ + 'Nette\Application\Application' => 'getApplication', + 'Throwable' => 'getThrowable', + ], + 'Contributte\Events\Extra\Event\Application\StartupEvent' => [ + 'Nette\Application\Application' => 'getApplication', + ], + 'Contributte\Events\Extra\Event\Application\ErrorEvent' => [ + 'Nette\Application\Application' => 'getApplication', + 'Throwable' => 'getThrowable', + ], + 'Contributte\Events\Extra\Event\Application\PresenterEvent' => [ + 'Nette\Application\Application' => 'getApplication', + 'Nette\Application\IPresenter' => 'getPresenter', + ], + 'Contributte\Events\Extra\Event\Application\RequestEvent' => [ + 'Nette\Application\Application' => 'getApplication', + 'Nette\Application\Request' => 'getRequest', + ], + 'Contributte\Events\Extra\Event\Application\ResponseEvent' => [ + 'Nette\Application\Application' => 'getApplication', + 'Nette\Application\IResponse' => 'getResponse', + ], + // presenter + 'Contributte\Events\Extra\Event\Application\PresenterShutdownEvent' => [ + 'Nette\Application\IPresenter' => 'getPresenter', + 'Nette\Application\IResponse' => 'getResponse', + ], + 'Contributte\Events\Extra\Event\Application\PresenterStartupEvent' => [ + 'Nette\Application\UI\Presenter' => 'getPresenter', + ], + // nette/security + 'Contributte\Events\Extra\Event\Security\LoggedInEvent' => [ + 'Nette\Security\User' => 'getUser', + ], + 'Contributte\Events\Extra\Event\Security\LoggedOutEvent' => [ + 'Nette\Security\User' => 'getUser', + ], + // latte + 'Contributte\Events\Extra\Event\Latte\LatteCompileEvent' => [ + 'Latte\Engine' => 'getEngine', + ], + 'Contributte\Events\Extra\Event\Latte\TemplateCreateEvent' => [ + 'Nette\Bridges\ApplicationLatte\Template' => 'getTemplate', + ], + ]; + + /** + * @var NodeNameResolver + */ + private $nodeNameResolver; + + /** + * @var VariableNaming + */ + private $variableNaming; + + /** + * @var StaticTypeMapper + */ + private $staticTypeMapper; + + /** + * @var NodeComparator + */ + private $nodeComparator; + + public function __construct( + NodeNameResolver $nodeNameResolver, + StaticTypeMapper $staticTypeMapper, + VariableNaming $variableNaming, + NodeComparator $nodeComparator + ) { + $this->nodeNameResolver = $nodeNameResolver; + $this->variableNaming = $variableNaming; + $this->staticTypeMapper = $staticTypeMapper; + $this->nodeComparator = $nodeComparator; + } + + public function resolveGetterMethodByEventClassAndParam( + string $eventClass, + Param $param, + ?EventAndListenerTree $eventAndListenerTree = null + ): string { + $getterMethodsWithType = self::CONTRIBUTTE_EVENT_GETTER_METHODS_WITH_TYPE[$eventClass] ?? null; + + $paramType = $param->type; + + // unwrap nullable type + if ($paramType instanceof NullableType) { + $paramType = $paramType->type; + } + + if ($eventAndListenerTree !== null) { + $getterMethodBlueprints = $eventAndListenerTree->getGetterMethodBlueprints(); + + foreach ($getterMethodBlueprints as $getterMethodBlueprint) { + if (! $getterMethodBlueprint->getReturnTypeNode() instanceof Name) { + continue; + } + + if ($this->nodeComparator->areNodesEqual( + $getterMethodBlueprint->getReturnTypeNode(), + $paramType + )) { + return $getterMethodBlueprint->getMethodName(); + } + } + } + + if ($paramType === null || $paramType instanceof Identifier) { + if ($paramType === null) { + $staticType = new MixedType(); + } else { + $staticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($paramType); + } + + return $this->createGetterFromParamAndStaticType($param, $staticType); + } + + $type = $this->nodeNameResolver->getName($paramType); + if ($type === null) { + throw new ShouldNotHappenException(); + } + + // system contribute event + if (isset($getterMethodsWithType[$type])) { + return $getterMethodsWithType[$type]; + } + + $paramName = $this->nodeNameResolver->getName($param->var); + if ($eventAndListenerTree !== null) { + $getterMethodBlueprints = $eventAndListenerTree->getGetterMethodBlueprints(); + + foreach ($getterMethodBlueprints as $getterMethodBlueprint) { + if ($getterMethodBlueprint->getVariableName() === $paramName) { + return $getterMethodBlueprint->getMethodName(); + } + } + } + + $staticType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($paramType); + + return $this->createGetterFromParamAndStaticType($param, $staticType); + } + + private function createGetterFromParamAndStaticType(Param $param, Type $type): string + { + $variableName = $this->variableNaming->resolveFromNodeAndType($param, $type); + if ($variableName === null) { + throw new ShouldNotHappenException(); + } + + return 'get' . ucfirst($variableName); + } +} diff --git a/src/NetteKdyby/DataProvider/EventAndListenerTreeProvider.php b/src/NetteKdyby/DataProvider/EventAndListenerTreeProvider.php new file mode 100644 index 0000000..cb60936 --- /dev/null +++ b/src/NetteKdyby/DataProvider/EventAndListenerTreeProvider.php @@ -0,0 +1,219 @@ +onPropertyMagicCallProvider = $onPropertyMagicCallProvider; + $this->listeningMethodsCollector = $listeningMethodsCollector; + $this->nodeNameResolver = $nodeNameResolver; + $this->eventClassNaming = $eventClassNaming; + $this->eventValueObjectClassFactory = $eventValueObjectClassFactory; + $this->dispatchMethodCallFactory = $dispatchMethodCallFactory; + $this->getSubscribedEventsClassMethodProvider = $getSubscribedEventsClassMethodProvider; + } + + public function matchMethodCall(MethodCall $methodCall): ?EventAndListenerTree + { + $this->initializeEventAndListenerTrees(); + + foreach ($this->eventAndListenerTrees as $eventAndListenerTree) { + if ($eventAndListenerTree->getMagicDispatchMethodCall() !== $methodCall) { + continue; + } + + return $eventAndListenerTree; + } + + return null; + } + + /** + * @return EventAndListenerTree[] + */ + public function provide(): array + { + $this->initializeEventAndListenerTrees(); + + return $this->eventAndListenerTrees; + } + + private function initializeEventAndListenerTrees(): void + { + if ($this->eventAndListenerTrees !== [] && ! StaticPHPUnitEnvironment::isPHPUnitRun()) { + return; + } + + $this->eventAndListenerTrees = []; + + foreach ($this->onPropertyMagicCallProvider->provide() as $methodCall) { + $magicProperty = $this->resolveMagicProperty($methodCall); + + $eventClassName = $this->eventClassNaming->createEventClassNameFromMethodCall($methodCall); + + $eventFileLocation = $this->eventClassNaming->resolveEventFileLocationFromMethodCall($methodCall); + + $eventClassInNamespace = $this->eventValueObjectClassFactory->create( + $eventClassName, + $methodCall->args + ); + + $dispatchMethodCall = $this->dispatchMethodCallFactory->createFromEventClassName($eventClassName); + + // getter names by variable name and type + $getterMethodsBlueprints = $this->resolveGetterMethodBlueprint($eventClassInNamespace); + + $eventAndListenerTree = new EventAndListenerTree( + $methodCall, + $magicProperty, + $eventClassName, + $eventFileLocation, + $eventClassInNamespace, + $dispatchMethodCall, + $this->getListeningClassMethodsInEventSubscriberByClass($eventClassName), + $getterMethodsBlueprints + ); + + $this->eventAndListenerTrees[] = $eventAndListenerTree; + } + } + + private function resolveMagicProperty(MethodCall $methodCall): ?Property + { + /** @var string $methodName */ + $methodName = $this->nodeNameResolver->getName($methodCall->name); + + /** @var Class_ $classLike */ + $classLike = $methodCall->getAttribute(AttributeKey::CLASS_NODE); + + return $classLike->getProperty($methodName); + } + + /** + * @return array + */ + private function getListeningClassMethodsInEventSubscriberByClass(string $eventClassName): array + { + $listeningClassMethodsByClass = []; + + foreach ($this->getSubscribedEventsClassMethodProvider->provide() as $getSubscribedClassMethod) { + /** @var string $className */ + $className = $getSubscribedClassMethod->getAttribute(AttributeKey::CLASS_NAME); + + $listeningClassMethods = $this->listeningMethodsCollector->classMethodsListeningToEventClass( + $getSubscribedClassMethod, + ListeningMethodsCollector::EVENT_TYPE_CUSTOM, + $eventClassName + ); + + $listeningClassMethodsByClass[$className] = $listeningClassMethods; + } + + return $listeningClassMethodsByClass; + } + + /** + * @return GetterMethodBlueprint[] + */ + private function resolveGetterMethodBlueprint(Namespace_ $eventClassInNamespace): array + { + /** @var Class_ $eventClass */ + $eventClass = $eventClassInNamespace->stmts[0]; + $getterMethodBlueprints = []; + + foreach ($eventClass->getMethods() as $classMethod) { + if (! $this->nodeNameResolver->isName($classMethod, 'get*')) { + continue; + } + + $stmts = (array) $classMethod->stmts; + + /** @var Return_ $return */ + $return = $stmts[0]; + + /** @var PropertyFetch $propertyFetch */ + $propertyFetch = $return->expr; + + $classMethodName = $this->nodeNameResolver->getName($classMethod); + + /** @var string $variableName */ + $variableName = $this->nodeNameResolver->getName($propertyFetch->name); + + $getterMethodBlueprints[] = new GetterMethodBlueprint( + $classMethodName, + $classMethod->returnType, + $variableName + ); + } + + return $getterMethodBlueprints; + } +} diff --git a/src/NetteKdyby/DataProvider/GetSubscribedEventsClassMethodProvider.php b/src/NetteKdyby/DataProvider/GetSubscribedEventsClassMethodProvider.php new file mode 100644 index 0000000..4bb5fc3 --- /dev/null +++ b/src/NetteKdyby/DataProvider/GetSubscribedEventsClassMethodProvider.php @@ -0,0 +1,32 @@ +nodeRepository = $nodeRepository; + } + + /** + * @return ClassMethod[] + */ + public function provide(): array + { + return $this->nodeRepository->findClassMethodByTypeAndMethod( + 'Kdyby\Events\Subscriber', + 'getSubscribedEvents' + ); + } +} diff --git a/src/NetteKdyby/DataProvider/OnPropertyMagicCallProvider.php b/src/NetteKdyby/DataProvider/OnPropertyMagicCallProvider.php new file mode 100644 index 0000000..e9bb895 --- /dev/null +++ b/src/NetteKdyby/DataProvider/OnPropertyMagicCallProvider.php @@ -0,0 +1,115 @@ +nodeRepository = $nodeRepository; + $this->nodeNameResolver = $nodeNameResolver; + } + + /** + * @return MethodCall[] + */ + public function provide(): array + { + if ($this->onPropertyMagicCalls !== [] && ! StaticPHPUnitEnvironment::isPHPUnitRun()) { + return $this->onPropertyMagicCalls; + } + + foreach ($this->nodeRepository->getMethodsCalls() as $methodCall) { + $scope = $methodCall->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + continue; + } + + if (! $this->isLocalOnPropertyCall($methodCall, $scope)) { + continue; + } + + $this->onPropertyMagicCalls[] = $methodCall; + } + + return $this->onPropertyMagicCalls; + } + + /** + * Detects method call on, e.g: + * public $onSomeProperty; + */ + private function isLocalOnPropertyCall(MethodCall $methodCall, Scope $scope): bool + { + if ($methodCall->var instanceof StaticCall) { + return false; + } + + if ($methodCall->var instanceof MethodCall) { + return false; + } + + if (! $this->nodeNameResolver->isName($methodCall->var, 'this')) { + return false; + } + + if (! $this->nodeNameResolver->isName($methodCall->name, 'on*')) { + return false; + } + + $methodName = $this->nodeNameResolver->getName($methodCall->name); + if ($methodName === null) { + return false; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + // control event, inner only + if ($classReflection->isSubclassOf(self::CONTROL_CLASS)) { + return false; + } + + if ($classReflection->hasMethod($methodName)) { + return false; + } + + return $classReflection->hasProperty($methodName); + } +} diff --git a/src/NetteKdyby/Naming/EventClassNaming.php b/src/NetteKdyby/Naming/EventClassNaming.php new file mode 100644 index 0000000..8cd9459 --- /dev/null +++ b/src/NetteKdyby/Naming/EventClassNaming.php @@ -0,0 +1,119 @@ +nodeNameResolver = $nodeNameResolver; + $this->classNaming = $classNaming; + } + + /** + * "App\SomeNamespace\SomeClass::onUpload" + * ↓ + * "App\SomeNamespace\Event\SomeClassUploadEvent" + */ + public function createEventClassNameFromMethodCall(MethodCall $methodCall): string + { + $shortEventClassName = $this->getShortEventClassName($methodCall); + + /** @var string $className */ + $className = $methodCall->getAttribute(AttributeKey::CLASS_NAME); + + return $this->prependShortClassEventWithNamespace($shortEventClassName, $className); + } + + public function resolveEventFileLocationFromMethodCall(MethodCall $methodCall): string + { + $shortEventClassName = $this->getShortEventClassName($methodCall); + + /** @var SmartFileInfo $fileInfo */ + $fileInfo = $methodCall->getAttribute(AttributeKey::FILE_INFO); + + return $fileInfo->getPath() . DIRECTORY_SEPARATOR . self::EVENT . DIRECTORY_SEPARATOR . $shortEventClassName . '.php'; + } + + public function resolveEventFileLocationFromClassNameAndFileInfo( + string $className, + SmartFileInfo $smartFileInfo + ): string { + $shortClassName = $this->nodeNameResolver->getShortName($className); + + return $smartFileInfo->getPath() . DIRECTORY_SEPARATOR . self::EVENT . DIRECTORY_SEPARATOR . $shortClassName . '.php'; + } + + public function createEventClassNameFromClassAndProperty(string $className, string $methodName): string + { + $shortEventClass = $this->createShortEventClassNameFromClassAndProperty($className, $methodName); + + return $this->prependShortClassEventWithNamespace($shortEventClass, $className); + } + + public function createEventClassNameFromClassPropertyReference(string $classAndPropertyName): string + { + [$class, $property] = explode('::', $classAndPropertyName); + + $shortEventClass = $this->createShortEventClassNameFromClassAndProperty($class, $property); + + return $this->prependShortClassEventWithNamespace($shortEventClass, $class); + } + + private function getShortEventClassName(MethodCall $methodCall): string + { + /** @var string $methodName */ + $methodName = $this->nodeNameResolver->getName($methodCall->name); + + /** @var string $className */ + $className = $methodCall->getAttribute(AttributeKey::CLASS_NAME); + + return $this->createShortEventClassNameFromClassAndProperty($className, $methodName); + } + + private function prependShortClassEventWithNamespace(string $shortEventClassName, string $orinalClassName): string + { + $namespaceAbove = Strings::before($orinalClassName, '\\', -1); + + return $namespaceAbove . '\\Event\\' . $shortEventClassName; + } + + /** + * TomatoMarket, onBuy + * ↓ + * TomatoMarketBuyEvent + */ + private function createShortEventClassNameFromClassAndProperty(string $class, string $property): string + { + $shortClassName = $this->classNaming->getShortName($class); + + // "onMagic" => "Magic" + $shortPropertyName = Strings::substring($property, strlen('on')); + + return $shortClassName . $shortPropertyName . self::EVENT; + } +} diff --git a/src/NetteKdyby/Naming/VariableNaming.php b/src/NetteKdyby/Naming/VariableNaming.php new file mode 100644 index 0000000..ab407f2 --- /dev/null +++ b/src/NetteKdyby/Naming/VariableNaming.php @@ -0,0 +1,272 @@ +nodeNameResolver = $nodeNameResolver; + $this->valueResolver = $valueResolver; + $this->nodeTypeResolver = $nodeTypeResolver; + } + + public function resolveFromNode(Node $node): ?string + { + $nodeType = $this->nodeTypeResolver->getStaticType($node); + return $this->resolveFromNodeAndType($node, $nodeType); + } + + public function resolveFromNodeAndType(Node $node, Type $type): ?string + { + $variableName = $this->resolveBareFromNode($node); + if ($variableName === null) { + return null; + } + + // adjust static to specific class + if ($variableName === 'this' && $type instanceof ThisType) { + $shortClassName = $this->nodeNameResolver->getShortName($type->getClassName()); + $variableName = lcfirst($shortClassName); + } + + $stringy = new Stringy($variableName); + return (string) $stringy->camelize(); + } + + public function resolveFromNodeWithScopeCountAndFallbackName( + Expr $expr, + Scope $scope, + string $fallbackName + ): string { + $name = $this->resolveFromNode($expr); + if ($name === null) { + $name = $fallbackName; + } + + if (Strings::contains($name, '\\')) { + $name = (string) Strings::after($name, '\\', - 1); + } + + $countedValueName = $this->createCountedValueName($name, $scope); + return lcfirst($countedValueName); + } + + public function createCountedValueName(string $valueName, ?Scope $scope): string + { + if ($scope === null) { + return $valueName; + } + + // make sure variable name is unique + if (! $scope->hasVariableType($valueName)->yes()) { + return $valueName; + } + + // we need to add number suffix until the variable is unique + $i = 2; + $countedValueNamePart = $valueName; + while ($scope->hasVariableType($valueName)->yes()) { + $valueName = $countedValueNamePart . $i; + ++$i; + } + + return $valueName; + } + + public function resolveFromFuncCallFirstArgumentWithSuffix( + FuncCall $funcCall, + string $suffix, + string $fallbackName, + ?Scope $scope + ): string { + $bareName = $this->resolveBareFuncCallArgumentName($funcCall, $fallbackName, $suffix); + return $this->createCountedValueName($bareName, $scope); + } + + private function resolveBareFromNode(Node $node): ?string + { + $node = $this->unwrapNode($node); + + if ($node instanceof ArrayDimFetch) { + return $this->resolveParamNameFromArrayDimFetch($node); + } + + if ($node instanceof PropertyFetch) { + return $this->resolveFromPropertyFetch($node); + } + + if ($node !== null && StaticInstanceOf::isOneOf( + $node, + [MethodCall::class, NullsafeMethodCall::class, StaticCall::class])) { + return $this->resolveFromMethodCall($node); + } + + if ($node instanceof New_) { + return $this->resolveFromNew($node); + } + + if ($node instanceof FuncCall) { + return $this->resolveFromNode($node->name); + } + + if (! $node instanceof Node) { + throw new NotImplementedYetException(); + } + + $paramName = $this->nodeNameResolver->getName($node); + if ($paramName !== null) { + return $paramName; + } + + if ($node instanceof String_) { + return $node->value; + } + + return null; + } + + private function resolveBareFuncCallArgumentName(FuncCall $funcCall, string $fallbackName, string $suffix): string + { + $argumentValue = $funcCall->args[0]->value; + if ($argumentValue instanceof MethodCall || $argumentValue instanceof StaticCall) { + $name = $this->nodeNameResolver->getName($argumentValue->name); + } else { + $name = $this->nodeNameResolver->getName($argumentValue); + } + + if ($name === null) { + return $fallbackName; + } + + return $name . $suffix; + } + + private function unwrapNode(Node $node): ?Node + { + if ($node instanceof Arg) { + return $node->value; + } + + if ($node instanceof Cast) { + return $node->expr; + } + + if ($node instanceof Ternary) { + return $node->if; + } + + return $node; + } + + private function resolveParamNameFromArrayDimFetch(ArrayDimFetch $arrayDimFetch): ?string + { + while ($arrayDimFetch instanceof ArrayDimFetch) { + if ($arrayDimFetch->dim instanceof Scalar) { + $valueName = $this->nodeNameResolver->getName($arrayDimFetch->var); + $dimName = $this->valueResolver->getValue($arrayDimFetch->dim); + + $stringy = new Stringy($dimName); + $dimName = (string) $stringy->upperCamelize(); + + return $valueName . $dimName; + } + + $arrayDimFetch = $arrayDimFetch->var; + } + + return $this->resolveBareFromNode($arrayDimFetch); + } + + private function resolveFromPropertyFetch(PropertyFetch $propertyFetch): string + { + $varName = $this->nodeNameResolver->getName($propertyFetch->var); + if (! is_string($varName)) { + throw new NotImplementedYetException(); + } + + $propertyName = $this->nodeNameResolver->getName($propertyFetch->name); + if (! is_string($propertyName)) { + throw new NotImplementedYetException(); + } + + if ($varName === 'this') { + return $propertyName; + } + + return $varName . ucfirst($propertyName); + } + + /** + * @param MethodCall|NullsafeMethodCall|StaticCall $node + */ + private function resolveFromMethodCall(Node $node): ?string + { + if ($node->name instanceof MethodCall) { + return $this->resolveFromMethodCall($node->name); + } + + $methodName = $this->nodeNameResolver->getName($node->name); + if (! is_string($methodName)) { + return null; + } + + return $methodName; + } + + private function resolveFromNew(New_ $new): string + { + if ($new->class instanceof Name) { + $className = $this->nodeNameResolver->getName($new->class); + return $this->nodeNameResolver->getShortName($className); + } + + throw new NotImplementedYetException(); + } +} diff --git a/src/NetteKdyby/NodeAnalyzer/GetSubscribedEventsClassMethodAnalyzer.php b/src/NetteKdyby/NodeAnalyzer/GetSubscribedEventsClassMethodAnalyzer.php new file mode 100644 index 0000000..f33cd72 --- /dev/null +++ b/src/NetteKdyby/NodeAnalyzer/GetSubscribedEventsClassMethodAnalyzer.php @@ -0,0 +1,45 @@ +nodeTypeResolver = $nodeTypeResolver; + $this->nodeNameResolver = $nodeNameResolver; + } + + public function detect(ClassMethod $classMethod): bool + { + $classLike = $classMethod->getAttribute(AttributeKey::CLASS_NODE); + if (! $classLike instanceof ClassLike) { + return false; + } + + if (! $this->nodeTypeResolver->isObjectType($classLike, new ObjectType('Kdyby\Events\Subscriber'))) { + return false; + } + + return $this->nodeNameResolver->isName($classMethod, 'getSubscribedEvents'); + } +} diff --git a/src/NetteKdyby/NodeFactory/DispatchMethodCallFactory.php b/src/NetteKdyby/NodeFactory/DispatchMethodCallFactory.php new file mode 100644 index 0000000..da27988 --- /dev/null +++ b/src/NetteKdyby/NodeFactory/DispatchMethodCallFactory.php @@ -0,0 +1,35 @@ +classNaming = $classNaming; + } + + public function createFromEventClassName(string $eventClassName): MethodCall + { + $shortEventClassName = $this->classNaming->getVariableName($eventClassName); + + $eventDispatcherPropertyFetch = new PropertyFetch(new Variable('this'), 'eventDispatcher'); + $dispatchMethodCall = new MethodCall($eventDispatcherPropertyFetch, 'dispatch'); + $dispatchMethodCall->args[] = new Arg(new Variable($shortEventClassName)); + + return $dispatchMethodCall; + } +} diff --git a/src/NetteKdyby/NodeFactory/EventValueObjectClassFactory.php b/src/NetteKdyby/NodeFactory/EventValueObjectClassFactory.php new file mode 100644 index 0000000..aee18ce --- /dev/null +++ b/src/NetteKdyby/NodeFactory/EventValueObjectClassFactory.php @@ -0,0 +1,181 @@ +classNaming = $classNaming; + $this->variableWithTypesFactory = $variableWithTypesFactory; + $this->nodeFactory = $nodeFactory; + $this->nodeNameResolver = $nodeNameResolver; + } + + /** + * @param Arg[] $args + */ + public function create(string $className, array $args): Namespace_ + { + $classBuilder = $this->createEventClassBuilder($className); + $this->decorateWithConstructorIfHasArgs($classBuilder, $args); + + $class = $classBuilder->getNode(); + + return $this->wrapClassToNamespace($className, $class); + } + + private function createEventClassBuilder(string $className): ClassBuilder + { + $shortClassName = $this->classNaming->getShortName($className); + + $classBuilder = new ClassBuilder($shortClassName); + $classBuilder->makeFinal(); + $classBuilder->extend(new FullyQualified('Symfony\Contracts\EventDispatcher\Event')); + + return $classBuilder; + } + + /** + * @param Arg[] $args + */ + private function decorateWithConstructorIfHasArgs(ClassBuilder $classBuilder, array $args): void + { + if ($args === []) { + return; + } + + $variablesWithTypes = $this->variableWithTypesFactory->createVariablesWithTypesFromArgs($args); + + $this->ensureVariablesAreUnique($variablesWithTypes, $classBuilder); + + $classMethod = $this->createConstructClassMethod($variablesWithTypes); + $classBuilder->addStmt($classMethod); + + // add properties + foreach ($variablesWithTypes as $variableWithType) { + $property = $this->nodeFactory->createPrivatePropertyFromNameAndType( + $variableWithType->getName(), + $variableWithType->getType() + ); + + $classBuilder->addStmt($property); + } + + // add getters + foreach ($variablesWithTypes as $variableWithType) { + $getterClassMethod = $this->nodeFactory->createGetterClassMethodFromNameAndType( + $variableWithType->getName(), + $variableWithType->getPhpParserTypeNode() + ); + + $classBuilder->addStmt($getterClassMethod); + } + } + + private function wrapClassToNamespace(string $className, Class_ $class): Namespace_ + { + $namespace = Strings::before($className, '\\', -1); + + $namespaceBuilder = new NamespaceBuilder($namespace); + $namespaceBuilder->addStmt($class); + + return $namespaceBuilder->getNode(); + } + + /** + * @param VariableWithType[] $variablesWithTypes + */ + private function ensureVariablesAreUnique(array $variablesWithTypes, ClassBuilder $classBuilder): void + { + $usedVariableNames = []; + + foreach ($variablesWithTypes as $variablesWithType) { + if (in_array($variablesWithType->getName(), $usedVariableNames, true)) { + $className = $this->nodeNameResolver->getName($classBuilder->getNode()); + + $message = sprintf( + 'Variable "$%s" is duplicated in to be created "%s" class', + $variablesWithType->getName(), + $className + ); + + throw new ShouldNotHappenException($message); + } + + $usedVariableNames[] = $variablesWithType->getName(); + } + } + + /** + * @param VariableWithType[] $variableWithTypes + */ + private function createConstructClassMethod(array $variableWithTypes): ClassMethod + { + $methodBuilder = new MethodBuilder(MethodName::CONSTRUCT); + $methodBuilder->makePublic(); + + foreach ($variableWithTypes as $variableWithType) { + $param = new Param(new Variable($variableWithType->getName())); + + if ($variableWithType->getPhpParserTypeNode() !== null) { + $param->type = $variableWithType->getPhpParserTypeNode(); + } + + $methodBuilder->addParam($param); + + $assign = $this->nodeFactory->createPropertyAssignment($variableWithType->getName()); + $methodBuilder->addStmt($assign); + } + + return $methodBuilder->getNode(); + } +} diff --git a/src/NetteKdyby/NodeManipulator/GetSubscribedEventsArrayManipulator.php b/src/NetteKdyby/NodeManipulator/GetSubscribedEventsArrayManipulator.php new file mode 100644 index 0000000..28e81e7 --- /dev/null +++ b/src/NetteKdyby/NodeManipulator/GetSubscribedEventsArrayManipulator.php @@ -0,0 +1,60 @@ +simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->valueResolver = $valueResolver; + } + + public function change(Array_ $array): void + { + $arrayItems = array_filter($array->items, function (ArrayItem $arrayItem): bool { + return $arrayItem !== null; + }); + + $this->simpleCallableNodeTraverser->traverseNodesWithCallable($arrayItems, function (Node $node): ?Node { + if (! $node instanceof ArrayItem) { + return null; + } + + foreach (NetteEventToContributeEventClass::PROPERTY_TO_EVENT_CLASS as $netteEventProperty => $contributeEventClass) { + if ($node->key === null) { + continue; + } + + if (! $this->valueResolver->isValue($node->key, $netteEventProperty)) { + continue; + } + + $node->key = new ClassConstFetch(new FullyQualified($contributeEventClass), 'class'); + } + + return $node; + }); + } +} diff --git a/src/NetteKdyby/NodeManipulator/ListeningClassMethodArgumentManipulator.php b/src/NetteKdyby/NodeManipulator/ListeningClassMethodArgumentManipulator.php new file mode 100644 index 0000000..0ec0178 --- /dev/null +++ b/src/NetteKdyby/NodeManipulator/ListeningClassMethodArgumentManipulator.php @@ -0,0 +1,136 @@ +classNaming = $classNaming; + $this->contributeEventClassResolver = $contributeEventClassResolver; + $this->paramAnalyzer = $paramAnalyzer; + } + + public function changeFromEventAndListenerTreeAndCurrentClassName( + EventAndListenerTree $eventAndListenerTree, + string $className + ): void { + $listenerClassMethods = $eventAndListenerTree->getListenerClassMethodsByClass($className); + if ($listenerClassMethods === []) { + return; + } + + $classMethodsByEventClass = []; + foreach ($listenerClassMethods as $listenerClassMethod) { + $classMethodsByEventClass[] = new EventClassAndClassMethod($className, $listenerClassMethod); + } + + $this->change($classMethodsByEventClass, $eventAndListenerTree); + } + + /** + * @param EventClassAndClassMethod[] $classMethodsByEventClass + */ + public function change(array $classMethodsByEventClass, ?EventAndListenerTree $eventAndListenerTree = null): void + { + foreach ($classMethodsByEventClass as $classMethods) { + // are attributes already replaced + $classMethod = $classMethods->getClassMethod(); + $eventParameterReplaced = $classMethod->getAttribute(self::EVENT_PARAMETER_REPLACED); + if ($eventParameterReplaced) { + continue; + } + + $oldParams = $classMethod->params; + + $eventClass = $eventAndListenerTree !== null ? $eventAndListenerTree->getEventClassName() : $classMethods->getEventClass(); + + $this->changeClassParamToEventClass($eventClass, $classMethod); + + // move params to getter on event + foreach ($oldParams as $oldParam) { + if (! $this->paramAnalyzer->isParamUsedInClassMethod($classMethod, $oldParam)) { + continue; + } + + $eventGetterToVariableAssign = $this->createEventGetterToVariableMethodCall( + $eventClass, + $oldParam, + $eventAndListenerTree + ); + + $expression = new Expression($eventGetterToVariableAssign); + + $classMethod->stmts = array_merge([$expression], (array) $classMethod->stmts); + } + + $classMethod->setAttribute(self::EVENT_PARAMETER_REPLACED, true); + } + } + + private function changeClassParamToEventClass(string $eventClass, ClassMethod $classMethod): void + { + $paramName = $this->classNaming->getVariableName($eventClass); + $eventVariable = new Variable($paramName); + + $param = new Param($eventVariable, null, new FullyQualified($eventClass)); + $classMethod->params = [$param]; + } + + private function createEventGetterToVariableMethodCall( + string $eventClass, + Param $param, + ?EventAndListenerTree $eventAndListenerTree = null + ): Assign { + $paramName = $this->classNaming->getVariableName($eventClass); + $eventVariable = new Variable($paramName); + + $getterMethod = $this->contributeEventClassResolver->resolveGetterMethodByEventClassAndParam( + $eventClass, + $param, + $eventAndListenerTree + ); + + $methodCall = new MethodCall($eventVariable, $getterMethod); + + return new Assign($param->var, $methodCall); + } +} diff --git a/src/NetteKdyby/NodeManipulator/ParamAnalyzer.php b/src/NetteKdyby/NodeManipulator/ParamAnalyzer.php new file mode 100644 index 0000000..c35d681 --- /dev/null +++ b/src/NetteKdyby/NodeManipulator/ParamAnalyzer.php @@ -0,0 +1,58 @@ +betterNodeFinder = $betterNodeFinder; + $this->nodeComparator = $nodeComparator; + } + + public function isParamUsedInClassMethod(ClassMethod $classMethod, Param $param): bool + { + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use ( + $param + ): bool { + if (! $node instanceof Variable) { + return false; + } + + return $this->nodeComparator->areNodesEqual($node, $param->var); + }); + } + + /** + * @param Param[] $params + */ + public function hasPropertyPromotion(array $params): bool + { + foreach ($params as $param) { + if ($param->flags !== 0) { + return true; + } + } + + return false; + } +} diff --git a/src/NetteKdyby/NodeResolver/ListeningMethodsCollector.php b/src/NetteKdyby/NodeResolver/ListeningMethodsCollector.php new file mode 100644 index 0000000..1c16a48 --- /dev/null +++ b/src/NetteKdyby/NodeResolver/ListeningMethodsCollector.php @@ -0,0 +1,205 @@ +simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->valueResolver = $valueResolver; + $this->eventClassNaming = $eventClassNaming; + } + + /** + * @return EventClassAndClassMethod[] + */ + public function collectFromClassAndGetSubscribedEventClassMethod( + ClassMethod $getSubscribedEventsClassMethod, + string $type + ): array { + /** @var Class_ $classLike */ + $classLike = $getSubscribedEventsClassMethod->getAttribute(AttributeKey::CLASS_NODE); + + $this->eventClassesAndClassMethods = []; + + $this->simpleCallableNodeTraverser->traverseNodesWithCallable( + (array) $getSubscribedEventsClassMethod->stmts, + function (Node $node) use ($classLike, $type) { + $classMethod = $this->matchClassMethodByArrayItem($node, $classLike); + if (! $classMethod instanceof ClassMethod) { + return null; + } + + if (! $node instanceof ArrayItem) { + return; + } + + if ($node->key === null) { + return; + } + + $eventClass = $this->valueResolver->getValue($node->key); + + if ($type === self::EVENT_TYPE_CONTRIBUTTE) { + /** @var string $eventClass */ + $this->resolveContributeEventClassAndSubscribedClassMethod($eventClass, $classMethod); + return null; + } + + if (! $node instanceof ArrayItem) { + throw new ShouldNotHappenException(); + } + + $eventClassAndClassMethod = $this->resolveCustomClassMethodAndEventClass( + $node, + $classLike, + $eventClass + ); + + if (! $eventClassAndClassMethod instanceof EventClassAndClassMethod) { + return null; + } + + $this->eventClassesAndClassMethods[] = $eventClassAndClassMethod; + return null; + } + ); + + return $this->eventClassesAndClassMethods; + } + + /** + * @return ClassMethod[] + */ + public function classMethodsListeningToEventClass( + ClassMethod $getSubscribedEventsClassMethod, + string $type, + string $eventClassName + ): array { + $eventClassesAndClassMethods = $this->collectFromClassAndGetSubscribedEventClassMethod( + $getSubscribedEventsClassMethod, + $type + ); + + $classMethods = []; + foreach ($eventClassesAndClassMethods as $eventClassAndClassMethod) { + if ($eventClassAndClassMethod->getEventClass() !== $eventClassName) { + continue; + } + + $classMethods[] = $eventClassAndClassMethod->getClassMethod(); + } + + return $classMethods; + } + + private function matchClassMethodByArrayItem(Node $node, Class_ $class): ?ClassMethod + { + if (! $node instanceof ArrayItem) { + return null; + } + + if ($node->key === null) { + return null; + } + + return $this->matchClassMethodByNodeValue($class, $node->value); + } + + private function resolveContributeEventClassAndSubscribedClassMethod( + string $eventClass, + ClassMethod $classMethod + ): void { + $contributeEventClasses = NetteEventToContributeEventClass::PROPERTY_TO_EVENT_CLASS; + + if (! in_array($eventClass, $contributeEventClasses, true)) { + return; + } + + $this->eventClassesAndClassMethods[] = new EventClassAndClassMethod($eventClass, $classMethod); + } + + private function resolveCustomClassMethodAndEventClass( + ArrayItem $arrayItem, + Class_ $class, + string $eventClass + ): ?EventClassAndClassMethod { + // custom method name + $classMethodName = $this->valueResolver->getValue($arrayItem->value); + $classMethod = $class->getMethod($classMethodName); + + if (Strings::contains($eventClass, '::')) { + [$dispatchingClass, $property] = explode('::', $eventClass); + $eventClass = $this->eventClassNaming->createEventClassNameFromClassAndProperty( + $dispatchingClass, + $property + ); + } + + if (! $classMethod instanceof ClassMethod) { + return null; + } + + return new EventClassAndClassMethod($eventClass, $classMethod); + } + + private function matchClassMethodByNodeValue(Class_ $class, Expr $expr): ?ClassMethod + { + $possibleMethodName = $this->valueResolver->getValue($expr); + if (! is_string($possibleMethodName)) { + return null; + } + + return $class->getMethod($possibleMethodName); + } +} diff --git a/src/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector.php b/src/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector.php new file mode 100644 index 0000000..6a1cb0b --- /dev/null +++ b/src/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector.php @@ -0,0 +1,192 @@ +getSubscribedEventsArrayManipulator = $getSubscribedEventsArrayManipulator; + $this->listeningClassMethodArgumentManipulator = $listeningClassMethodArgumentManipulator; + $this->listeningMethodsCollector = $listeningMethodsCollector; + $this->getSubscribedEventsClassMethodAnalyzer = $getSubscribedEventsClassMethodAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change EventSubscriber from Kdyby to Contributte', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Kdyby\Events\Subscriber; +use Nette\Application\Application; +use Nette\Application\UI\Presenter; + +class GetApplesSubscriber implements Subscriber +{ + public function getSubscribedEvents() + { + return [ + Application::class . '::onShutdown', + ]; + } + + public function onShutdown(Presenter $presenter) + { + $presenterName = $presenter->getName(); + // ... + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Contributte\Events\Extra\Event\Application\ShutdownEvent; +use Kdyby\Events\Subscriber; +use Nette\Application\Application; + +class GetApplesSubscriber implements Subscriber +{ + public static function getSubscribedEvents() + { + return [ + ShutdownEvent::class => 'onShutdown', + ]; + } + + public function onShutdown(ShutdownEvent $shutdownEvent) + { + $presenter = $shutdownEvent->getPresenter(); + $presenterName = $presenter->getName(); + // ... + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->getSubscribedEventsClassMethodAnalyzer->detect($node)) { + return null; + } + + $this->visibilityManipulator->makeStatic($node); + $this->refactorEventNames($node); + + $listeningClassMethods = $this->listeningMethodsCollector->collectFromClassAndGetSubscribedEventClassMethod( + $node, + ListeningMethodsCollector::EVENT_TYPE_CONTRIBUTTE + ); + + $this->listeningClassMethodArgumentManipulator->change($listeningClassMethods); + + return $node; + } + + private function refactorEventNames(ClassMethod $classMethod): void + { + $this->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) { + if (! $node instanceof Return_) { + return null; + } + + if ($node->expr === null) { + return null; + } + + $returnedExpr = $node->expr; + if (! $returnedExpr instanceof Array_) { + return null; + } + + $this->refactorArrayWithEventTable($returnedExpr); + + $this->getSubscribedEventsArrayManipulator->change($returnedExpr); + }); + } + + private function refactorArrayWithEventTable(Array_ $array): void + { + foreach ($array->items as $arrayItem) { + if ($arrayItem === null) { + continue; + } + + if ($arrayItem->key !== null) { + continue; + } + + $methodName = $this->resolveMethodNameFromKdybyEventName($arrayItem->value); + $arrayItem->key = $arrayItem->value; + $arrayItem->value = new String_($methodName); + } + } + + private function resolveMethodNameFromKdybyEventName(Expr $expr): string + { + $kdybyEventName = $this->valueResolver->getValue($expr); + if (Strings::contains($kdybyEventName, '::')) { + return (string) Strings::after($kdybyEventName, '::', - 1); + } + + throw new NotImplementedYetException($kdybyEventName); + } +} diff --git a/src/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector.php b/src/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector.php new file mode 100644 index 0000000..a9f9eeb --- /dev/null +++ b/src/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector.php @@ -0,0 +1,169 @@ +eventClassNaming = $eventClassNaming; + $this->listeningClassMethodArgumentManipulator = $listeningClassMethodArgumentManipulator; + $this->eventAndListenerTreeProvider = $eventAndListenerTreeProvider; + $this->getSubscribedEventsClassMethodAnalyzer = $getSubscribedEventsClassMethodAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change getSubscribedEvents() from on magic property, to Event class', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Kdyby\Events\Subscriber; + +final class ActionLogEventSubscriber implements Subscriber +{ + public function getSubscribedEvents(): array + { + return [ + AlbumService::class . '::onApprove' => 'onAlbumApprove', + ]; + } + + public function onAlbumApprove(Album $album, int $adminId): void + { + $album->play(); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Kdyby\Events\Subscriber; + +final class ActionLogEventSubscriber implements Subscriber +{ + public function getSubscribedEvents(): array + { + return [ + AlbumServiceApproveEvent::class => 'onAlbumApprove', + ]; + } + + public function onAlbumApprove(AlbumServiceApproveEventAlbum $albumServiceApproveEventAlbum): void + { + $album = $albumServiceApproveEventAlbum->getAlbum(); + $album->play(); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->getSubscribedEventsClassMethodAnalyzer->detect($node)) { + return null; + } + + $this->replaceEventPropertyReferenceWithEventClassReference($node); + + $eventAndListenerTrees = $this->eventAndListenerTreeProvider->provide(); + if ($eventAndListenerTrees === []) { + return null; + } + + /** @var string $className */ + $className = $node->getAttribute(AttributeKey::CLASS_NAME); + + foreach ($eventAndListenerTrees as $eventAndListenerTree) { + $this->listeningClassMethodArgumentManipulator->changeFromEventAndListenerTreeAndCurrentClassName( + $eventAndListenerTree, + $className + ); + } + + return $node; + } + + private function replaceEventPropertyReferenceWithEventClassReference(ClassMethod $classMethod): void + { + $this->traverseNodesWithCallable((array) $classMethod->stmts, function (Node $node) { + if (! $node instanceof ArrayItem) { + return null; + } + + $arrayKey = $node->key; + if (! $arrayKey instanceof Expr) { + return null; + } + + $eventPropertyReferenceName = $this->valueResolver->getValue($arrayKey); + + // is property? + if (! Strings::contains($eventPropertyReferenceName, '::')) { + return null; + } + + $eventClassName = $this->eventClassNaming->createEventClassNameFromClassPropertyReference( + $eventPropertyReferenceName + ); + + $node->key = $this->nodeFactory->createClassConstReference($eventClassName); + }); + } +} diff --git a/src/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector.php b/src/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector.php new file mode 100644 index 0000000..055b76f --- /dev/null +++ b/src/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector.php @@ -0,0 +1,193 @@ +eventClassNaming = $eventClassNaming; + $this->eventValueObjectClassFactory = $eventValueObjectClassFactory; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change Kdyby EventManager to EventDispatcher', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Kdyby\Events\EventManager; + +final class SomeClass +{ + /** + * @var EventManager + */ + private $eventManager; + + public function __construct(EventManager $eventManager) + { + $this->eventManager = eventManager; + } + + public function run() + { + $key = '2000'; + $this->eventManager->dispatchEvent(static::class . '::onCopy', new EventArgsList([$this, $key])); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +use Kdyby\Events\EventManager; + +final class SomeClass +{ + /** + * @var EventManager + */ + private $eventManager; + + public function __construct(EventManager $eventManager) + { + $this->eventManager = eventManager; + } + + public function run() + { + $key = '2000'; + $this->eventManager->dispatch(new SomeClassCopyEvent($this, $key)); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkip($node)) { + return null; + } + + $node->name = new Identifier('dispatch'); + + $oldArgs = $node->args; + $node->args = []; + + $eventReference = $oldArgs[0]->value; + + $classAndStaticProperty = $this->valueResolver->getValue($eventReference, true); + $eventClassName = $this->eventClassNaming->createEventClassNameFromClassPropertyReference( + $classAndStaticProperty + ); + + $args = $this->createNewArgs($oldArgs); + + $new = new New_(new FullyQualified($eventClassName), $args); + $node->args[] = new Arg($new); + + // 3. create new event class with args + $eventClassInNamespace = $this->eventValueObjectClassFactory->create($eventClassName, $args); + + $fileInfo = $node->getAttribute(AttributeKey::FILE_INFO); + if (! $fileInfo instanceof SmartFileInfo) { + throw new ShouldNotHappenException(); + } + + $eventFileLocation = $this->eventClassNaming->resolveEventFileLocationFromClassNameAndFileInfo( + $eventClassName, + $fileInfo + ); + + $addedFileWithNodes = new AddedFileWithNodes($eventFileLocation, [$eventClassInNamespace]); + $this->removedAndAddedFilesCollector->addAddedFile($addedFileWithNodes); + + return $node; + } + + private function shouldSkip(MethodCall $methodCall): bool + { + if (! $this->isObjectType($methodCall->var, new ObjectType('Kdyby\Events\EventManager'))) { + return true; + } + + return ! $this->isName($methodCall->name, 'dispatchEvent'); + } + + /** + * @param Arg[] $oldArgs + * @return Arg[] + */ + private function createNewArgs(array $oldArgs): array + { + $args = []; + + if ($oldArgs[1]->value instanceof New_) { + /** @var New_ $new */ + $new = $oldArgs[1]->value; + + $array = $new->args[0]->value; + if (! $array instanceof Array_) { + return []; + } + foreach ($array->items as $arrayItem) { + if (! $arrayItem instanceof ArrayItem) { + continue; + } + + $args[] = new Arg($arrayItem->value); + } + } + + return $args; + } +} diff --git a/src/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector.php b/src/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector.php new file mode 100644 index 0000000..6f88d76 --- /dev/null +++ b/src/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector.php @@ -0,0 +1,152 @@ +classNaming = $classNaming; + $this->eventAndListenerTreeProvider = $eventAndListenerTreeProvider; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change $onProperty magic call with event disptacher and class dispatch', + [ + new CodeSample( + <<<'CODE_SAMPLE' +final class FileManager +{ + public $onUpload; + + public function run(User $user) + { + $this->onUpload($user); + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +final class FileManager +{ + use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + + public function __construct(EventDispatcherInterface $eventDispatcher) + { + $this->eventDispatcher = $eventDispatcher; + } + + public function run(User $user) + { + $onFileManagerUploadEvent = new FileManagerUploadEvent($user); + $this->eventDispatcher->dispatch($onFileManagerUploadEvent); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + // 1. is onProperty? call + $eventAndListenerTree = $this->eventAndListenerTreeProvider->matchMethodCall($node); + if (! $eventAndListenerTree instanceof EventAndListenerTree) { + return null; + } + + // 2. guess event name + $eventClassName = $eventAndListenerTree->getEventClassName(); + + // 3. create new event class with args + $eventClassInNamespace = $eventAndListenerTree->getEventClassInNamespace(); + + $addedFileWithNodes = new AddedFileWithNodes($eventAndListenerTree->getEventFileLocation(), [ + $eventClassInNamespace, + ]); + $this->removedAndAddedFilesCollector->addAddedFile($addedFileWithNodes); + + // 4. ad dispatch method call + $dispatchMethodCall = $eventAndListenerTree->getEventDispatcherDispatchMethodCall(); + $this->addNodeAfterNode($dispatchMethodCall, $node); + + // 5. return event adding + // add event dispatcher dependency if needed + $assign = $this->createEventInstanceAssign($eventClassName, $node); + + /** @var Class_ $classLike */ + $classLike = $node->getAttribute(AttributeKey::CLASS_NODE); + $this->addConstructorDependencyToClass( + $classLike, + new FullyQualifiedObjectType('Symfony\Contracts\EventDispatcher\EventDispatcherInterface'), + 'eventDispatcher' + ); + + // 6. remove property + if ($eventAndListenerTree->getOnMagicProperty() !== null) { + $this->removeNode($eventAndListenerTree->getOnMagicProperty()); + } + + return $assign; + } + + private function createEventInstanceAssign(string $eventClassName, MethodCall $methodCall): Assign + { + $shortEventClassName = $this->classNaming->getVariableName($eventClassName); + + $new = new New_(new FullyQualified($eventClassName)); + + if ($methodCall->args) { + $new->args = $methodCall->args; + } + + return new Assign(new Variable($shortEventClassName), $new); + } +} diff --git a/src/NetteKdyby/ValueObject/EventAndListenerTree.php b/src/NetteKdyby/ValueObject/EventAndListenerTree.php new file mode 100644 index 0000000..a5a1e0f --- /dev/null +++ b/src/NetteKdyby/ValueObject/EventAndListenerTree.php @@ -0,0 +1,123 @@ + + */ + private $listenerMethodsByEventSubscriberClass = []; + + /** + * @var GetterMethodBlueprint[] + */ + private $getterMethodBlueprints = []; + + /** + * @var MethodCall + */ + private $magicDispatchMethodCall; + + /** + * @var Namespace_ + */ + private $eventClassInNamespace; + + /** + * @var MethodCall + */ + private $eventDispatcherDispatchMethodCall; + + /** + * @var Property|null + */ + private $onMagicProperty; + + /** + * @param array $listenerMethodsByEventSubscriberClass + * @param GetterMethodBlueprint[] $getterMethodsBlueprints + */ + public function __construct( + MethodCall $magicDispatchMethodCall, + ?Property $onMagicProperty, + string $eventClassName, + string $eventFileLocation, + Namespace_ $eventClassInNamespace, + MethodCall $eventDispatcherDispatchMethodCall, + array $listenerMethodsByEventSubscriberClass, + array $getterMethodsBlueprints + ) { + $this->magicDispatchMethodCall = $magicDispatchMethodCall; + $this->onMagicProperty = $onMagicProperty; + $this->eventClassName = $eventClassName; + $this->eventFileLocation = $eventFileLocation; + $this->eventClassInNamespace = $eventClassInNamespace; + $this->listenerMethodsByEventSubscriberClass = $listenerMethodsByEventSubscriberClass; + $this->eventDispatcherDispatchMethodCall = $eventDispatcherDispatchMethodCall; + $this->getterMethodBlueprints = $getterMethodsBlueprints; + } + + public function getEventClassName(): string + { + return $this->eventClassName; + } + + /** + * @return ClassMethod[] + */ + public function getListenerClassMethodsByClass(string $className): array + { + return $this->listenerMethodsByEventSubscriberClass[$className] ?? []; + } + + public function getOnMagicProperty(): ?Property + { + return $this->onMagicProperty; + } + + public function getEventFileLocation(): string + { + return $this->eventFileLocation; + } + + public function getMagicDispatchMethodCall(): MethodCall + { + return $this->magicDispatchMethodCall; + } + + public function getEventClassInNamespace(): Namespace_ + { + return $this->eventClassInNamespace; + } + + public function getEventDispatcherDispatchMethodCall(): MethodCall + { + return $this->eventDispatcherDispatchMethodCall; + } + + /** + * @return GetterMethodBlueprint[] + */ + public function getGetterMethodBlueprints(): array + { + return $this->getterMethodBlueprints; + } +} diff --git a/src/NetteKdyby/ValueObject/EventClassAndClassMethod.php b/src/NetteKdyby/ValueObject/EventClassAndClassMethod.php new file mode 100644 index 0000000..9864fe5 --- /dev/null +++ b/src/NetteKdyby/ValueObject/EventClassAndClassMethod.php @@ -0,0 +1,36 @@ +eventClass = $eventClass; + $this->classMethod = $classMethod; + } + + public function getEventClass(): string + { + return $this->eventClass; + } + + public function getClassMethod(): ClassMethod + { + return $this->classMethod; + } +} diff --git a/src/NetteKdyby/ValueObject/GetterMethodBlueprint.php b/src/NetteKdyby/ValueObject/GetterMethodBlueprint.php new file mode 100644 index 0000000..3ff5a53 --- /dev/null +++ b/src/NetteKdyby/ValueObject/GetterMethodBlueprint.php @@ -0,0 +1,47 @@ +methodName = $methodName; + $this->returnTypeNode = $returnTypeNode; + $this->variableName = $variableName; + } + + public function getMethodName(): string + { + return $this->methodName; + } + + public function getReturnTypeNode(): ?Node + { + return $this->returnTypeNode; + } + + public function getVariableName(): string + { + return $this->variableName; + } +} diff --git a/src/NetteKdyby/ValueObject/NetteEventToContributeEventClass.php b/src/NetteKdyby/ValueObject/NetteEventToContributeEventClass.php new file mode 100644 index 0000000..5a1d702 --- /dev/null +++ b/src/NetteKdyby/ValueObject/NetteEventToContributeEventClass.php @@ -0,0 +1,31 @@ + + * @see https://github.com/contributte/event-dispatcher-extra/tree/master/src/Event + */ + public const PROPERTY_TO_EVENT_CLASS = [ + // application + 'Nette\Application\Application::onShutdown' => 'Contributte\Events\Extra\Event\Application\ShutdownEvent', + 'Nette\Application\Application::onStartup' => 'Contributte\Events\Extra\Event\Application\StartupEvent', + 'Nette\Application\Application::onError' => 'Contributte\Events\Extra\Event\Application\ErrorEvent', + 'Nette\Application\Application::onPresenter' => 'Contributte\Events\Extra\Event\Application\PresenterEvent', + 'Nette\Application\Application::onRequest' => 'Contributte\Events\Extra\Event\Application\RequestEvent', + 'Nette\Application\Application::onResponse' => 'Contributte\Events\Extra\Event\Application\ResponseEvent', + // presenter + 'Nette\Application\UI\Presenter::onStartup' => 'Contributte\Events\Extra\Event\Application\PresenterShutdownEvent', + 'Nette\Application\UI\Presenter::onShutdown' => 'Contributte\Events\Extra\Event\Application\PresenterStartupEvent', + // nette/security + 'Nette\Security\User::onLoggedIn' => 'Contributte\Events\Extra\Event\Security\LoggedInEvent', + 'Nette\Security\User::onLoggedOut' => 'Contributte\Events\Extra\Event\Security\LoggedOutEvent', + // latte + 'Latte\Engine::onCompile' => 'Contributte\Events\Extra\Event\Latte\LatteCompileEvent', + 'Nette\Bridges\ApplicationLatte\TemplateFactory::onCreate' => 'Contributte\Events\Extra\Event\Latte\TemplateCreateEvent', + ]; +} diff --git a/src/NetteKdyby/ValueObject/VariableWithType.php b/src/NetteKdyby/ValueObject/VariableWithType.php new file mode 100644 index 0000000..f809f4d --- /dev/null +++ b/src/NetteKdyby/ValueObject/VariableWithType.php @@ -0,0 +1,58 @@ +name = $name; + $this->type = $staticType; + $this->phpParserTypeNode = $phpParserTypeNode; + } + + public function getName(): string + { + return $this->name; + } + + public function getType(): Type + { + return $this->type; + } + + /** + * @return Identifier|Name|NullableType|UnionType|null + */ + public function getPhpParserTypeNode(): ?Node + { + return $this->phpParserTypeNode; + } +} diff --git a/src/NetteTesterToPHPUnit/AssertManipulator.php b/src/NetteTesterToPHPUnit/AssertManipulator.php new file mode 100644 index 0000000..f190320 --- /dev/null +++ b/src/NetteTesterToPHPUnit/AssertManipulator.php @@ -0,0 +1,323 @@ + + */ + private const ASSERT_METHODS_REMAP = [ + 'same' => 'assertSame', + 'notSame' => 'assertNotSame', + 'equal' => 'assertEqual', + 'notEqual' => 'assertNotEqual', + 'true' => 'assertTrue', + 'false' => 'assertFalse', + 'null' => 'assertNull', + 'notNull' => 'assertNotNull', + 'count' => 'assertCount', + 'match' => 'assertStringMatchesFormat', + 'matchFile' => 'assertStringMatchesFormatFile', + 'nan' => 'assertIsNumeric', + ]; + + /** + * @var string[] + */ + private const TYPE_TO_METHOD = [ + 'list' => 'assertIsArray', + 'array' => 'assertIsArray', + 'bool' => 'assertIsBool', + 'callable' => 'assertIsCallable', + 'float' => 'assertIsFloat', + 'int' => 'assertIsInt', + 'integer' => 'assertIsInt', + 'object' => 'assertIsObject', + 'resource' => 'assertIsResource', + 'string' => 'assertIsString', + 'scalar' => 'assertIsScalar', + ]; + + /** + * @var string + */ + private const CONTAINS = 'contains'; + + /** + * @var string + */ + private const THIS = 'this'; + + /** + * @var string + */ + private const SELF = 'self'; + + /** + * @var NodeNameResolver + */ + private $nodeNameResolver; + + /** + * @var NodeTypeResolver + */ + private $nodeTypeResolver; + + /** + * @var ValueResolver + */ + private $valueResolver; + + /** + * @var StringTypeAnalyzer + */ + private $stringTypeAnalyzer; + + /** + * @var NodesToRemoveCollector + */ + private $nodesToRemoveCollector; + + /** + * @var NodesToAddCollector + */ + private $nodesToAddCollector; + + /** + * @var PhpDocInfoFactory + */ + private $phpDocInfoFactory; + + public function __construct( + NodeNameResolver $nodeNameResolver, + NodeTypeResolver $nodeTypeResolver, + NodesToAddCollector $nodesToAddCollector, + NodesToRemoveCollector $nodesToRemoveCollector, + StringTypeAnalyzer $stringTypeAnalyzer, + ValueResolver $valueResolver, + PhpDocInfoFactory $phpDocInfoFactory + ) { + $this->nodeNameResolver = $nodeNameResolver; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->valueResolver = $valueResolver; + $this->stringTypeAnalyzer = $stringTypeAnalyzer; + $this->nodesToRemoveCollector = $nodesToRemoveCollector; + $this->nodesToAddCollector = $nodesToAddCollector; + $this->phpDocInfoFactory = $phpDocInfoFactory; + } + + /** + * @return StaticCall|MethodCall + */ + public function processStaticCall(StaticCall $staticCall): Node + { + if ($this->nodeNameResolver->isNames($staticCall->name, ['truthy', 'falsey'])) { + return $this->processTruthyOrFalseyCall($staticCall); + } + + if ($this->nodeNameResolver->isNames($staticCall->name, [self::CONTAINS, 'notContains'])) { + $this->processContainsCall($staticCall); + } elseif ($this->nodeNameResolver->isNames($staticCall->name, ['exception', 'throws'])) { + $this->processExceptionCall($staticCall); + } elseif ($this->nodeNameResolver->isName($staticCall->name, 'type')) { + $this->processTypeCall($staticCall); + } elseif ($this->nodeNameResolver->isName($staticCall->name, 'noError')) { + $this->processNoErrorCall($staticCall); + } else { + $this->renameAssertMethod($staticCall); + } + + // self or class, depending on the context + // prefer $this->assertSame() as more conventional and explicit in class-context + if (! $this->sholdBeStaticCall($staticCall)) { + $methodCall = new MethodCall(new Variable(self::THIS), $staticCall->name); + $methodCall->args = $staticCall->args; + $methodCall->setAttributes($staticCall->getAttributes()); + $methodCall->setAttribute(AttributeKey::ORIGINAL_NODE, null); + + return $methodCall; + } + + $staticCall->class = new FullyQualified('PHPUnit\Framework\Assert'); + + return $staticCall; + } + + /** + * @return StaticCall|MethodCall + */ + private function processTruthyOrFalseyCall(StaticCall $staticCall): Expr + { + $method = $this->nodeNameResolver->isName($staticCall->name, 'truthy') ? 'assertTrue' : 'assertFalse'; + + if (! $this->sholdBeStaticCall($staticCall)) { + $call = new MethodCall(new Variable(self::THIS), $method); + $call->args = $staticCall->args; + $call->setAttributes($staticCall->getAttributes()); + $call->setAttribute(AttributeKey::ORIGINAL_NODE, null); + } else { + $call = $staticCall; + $call->name = new Identifier($method); + } + + if (! $this->nodeTypeResolver->isStaticType($staticCall->args[0]->value, BooleanType::class)) { + $call->args[0]->value = new Bool_($staticCall->args[0]->value); + } + + return $call; + } + + private function processContainsCall(StaticCall $staticCall): void + { + if ($this->stringTypeAnalyzer->isStringOrUnionStringOnlyType($staticCall->args[1]->value)) { + $name = $this->nodeNameResolver->isName( + $staticCall->name, + self::CONTAINS + ) ? 'assertStringContainsString' : 'assertStringNotContainsString'; + } else { + $name = $this->nodeNameResolver->isName( + $staticCall->name, + self::CONTAINS + ) ? 'assertContains' : 'assertNotContains'; + } + + $staticCall->name = new Identifier($name); + } + + private function processExceptionCall(StaticCall $staticCall): void + { + $method = 'expectException'; + + // expect exception + if ($this->sholdBeStaticCall($staticCall)) { + $expectException = new StaticCall(new Name(self::SELF), $method); + } else { + $expectException = new MethodCall(new Variable(self::THIS), $method); + } + + $expectException->args[] = $staticCall->args[1]; + $this->nodesToAddCollector->addNodeAfterNode($expectException, $staticCall); + + // expect message + if (isset($staticCall->args[2])) { + $this->refactorExpectException($staticCall); + } + + // expect code + if (isset($staticCall->args[3])) { + $this->refactorExpectExceptionCode($staticCall); + } + + /** @var Closure $closure */ + $closure = $staticCall->args[0]->value; + $this->nodesToAddCollector->addNodesAfterNode($closure->stmts, $staticCall); + + $this->nodesToRemoveCollector->addNodeToRemove($staticCall); + } + + private function processTypeCall(StaticCall $staticCall): void + { + $value = $this->valueResolver->getValue($staticCall->args[0]->value); + + if (isset(self::TYPE_TO_METHOD[$value])) { + $staticCall->name = new Identifier(self::TYPE_TO_METHOD[$value]); + unset($staticCall->args[0]); + $staticCall->args = array_values($staticCall->args); + } elseif ($value === 'null') { + $staticCall->name = new Identifier('assertNull'); + unset($staticCall->args[0]); + $staticCall->args = array_values($staticCall->args); + } else { + $staticCall->name = new Identifier('assertInstanceOf'); + } + } + + private function processNoErrorCall(StaticCall $staticCall): void + { + /** @var Closure $closure */ + $closure = $staticCall->args[0]->value; + + $this->nodesToAddCollector->addNodesAfterNode($closure->stmts, $staticCall); + $this->nodesToRemoveCollector->addNodeToRemove($staticCall); + + $classMethod = $staticCall->getAttribute(AttributeKey::METHOD_NODE); + if (! $classMethod instanceof ClassMethod) { + return; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); + $phpDocInfo->addPhpDocTagNode(new PHPUnitDoesNotPerformAssertionTagNode()); + } + + private function renameAssertMethod(StaticCall $staticCall): void + { + foreach (self::ASSERT_METHODS_REMAP as $oldMethod => $newMethod) { + if (! $this->nodeNameResolver->isName($staticCall->name, $oldMethod)) { + continue; + } + + $staticCall->name = new Identifier($newMethod); + } + } + + private function sholdBeStaticCall(StaticCall $staticCall): bool + { + return ! (bool) $staticCall->getAttribute(AttributeKey::CLASS_NODE); + } + + private function refactorExpectException(StaticCall $staticCall): string + { + $method = 'expectExceptionMessage'; + + if ($this->sholdBeStaticCall($staticCall)) { + $expectExceptionMessage = new StaticCall(new Name(self::SELF), $method); + } else { + $expectExceptionMessage = new MethodCall(new Variable(self::THIS), $method); + } + + $expectExceptionMessage->args[] = $staticCall->args[2]; + $this->nodesToAddCollector->addNodeAfterNode($expectExceptionMessage, $staticCall); + return $method; + } + + private function refactorExpectExceptionCode(StaticCall $staticCall): void + { + if ($this->sholdBeStaticCall($staticCall)) { + $expectExceptionCode = new StaticCall(new Name(self::SELF), 'expectExceptionCode'); + } else { + $expectExceptionCode = new MethodCall(new Variable(self::THIS), 'expectExceptionCode'); + } + + $expectExceptionCode->args[] = $staticCall->args[3]; + $this->nodesToAddCollector->addNodeAfterNode($expectExceptionCode, $staticCall); + } +} diff --git a/src/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php b/src/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php new file mode 100644 index 0000000..4b3dc5a --- /dev/null +++ b/src/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector.php @@ -0,0 +1,135 @@ +getService('kdyby.doctrine.default.entityManager'), $default); + } +} + +(new \ExtensionTest())->run(); +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +namespace KdybyTests\Doctrine; + +use Tester\TestCase; +use Tester\Assert; + +class ExtensionTest extends \PHPUnit\Framework\TestCase +{ + public function testFunctionality() + { + $this->assertInstanceOf(\Kdyby\Doctrine\EntityManager::cllass, $default); + $this->assertTrue(5); + $this->same($container->getService('kdyby.doctrine.default.entityManager'), $default); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class, Include_::class, MethodCall::class]; + } + + /** + * @param Class_|Include_|MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if ($node instanceof Include_) { + $this->processAboveTestInclude($node); + return null; + } + + if ($node instanceof MethodCall) { + $this->processUnderTestRun($node); + return null; + } + + if (! $this->isObjectType($node, new ObjectType('Tester\TestCase'))) { + return null; + } + + $this->processExtends($node); + $this->processMethods($node); + + return $node; + } + + private function processAboveTestInclude(Include_ $include): void + { + $classLike = $include->getAttribute(AttributeKey::CLASS_NODE); + if (! $classLike instanceof ClassLike) { + $this->removeNode($include); + } + } + + private function processUnderTestRun(MethodCall $methodCall): void + { + if (! $this->isObjectType($methodCall->var, new ObjectType('Tester\TestCase'))) { + return; + } + + if ($this->isName($methodCall->name, 'run')) { + $this->removeNode($methodCall); + } + } + + private function processExtends(Class_ $class): void + { + $class->extends = new FullyQualified('PHPUnit\Framework\TestCase'); + } + + private function processMethods(Class_ $class): void + { + foreach ($class->getMethods() as $classMethod) { + if ($this->isNames($classMethod, [MethodName::SET_UP, MethodName::TEAR_DOWN])) { + $this->visibilityManipulator->makeProtected($classMethod); + } + } + } +} diff --git a/src/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector.php b/src/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector.php new file mode 100644 index 0000000..4199e5a --- /dev/null +++ b/src/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector.php @@ -0,0 +1,89 @@ +> + */ + public function getNodeTypes(): array + { + return [FileNode::class]; + } + + /** + * @param FileNode $node + */ + public function refactor(Node $node): ?Node + { + $smartFileInfo = $node->getFileInfo(); + $oldRealPath = $smartFileInfo->getRealPath(); + if (! Strings::endsWith($oldRealPath, '.phpt')) { + return null; + } + + $newRealPath = $this->createNewRealPath($oldRealPath); + if ($newRealPath === $oldRealPath) { + return null; + } + + $movedFileWithContent = new MovedFileWithContent($smartFileInfo, $newRealPath); + $this->removedAndAddedFilesCollector->addMovedFile($movedFileWithContent); + + return null; + } + + private function createNewRealPath(string $oldRealPath): string + { + // file suffix + $newRealPath = Strings::replace($oldRealPath, self::PHPT_SUFFIX_REGEX, '.php'); + + // Test suffix + if (! Strings::endsWith($newRealPath, 'Test.php')) { + return Strings::replace($newRealPath, self::PHP_SUFFIX_REGEX, 'Test.php'); + } + + return $newRealPath; + } +} diff --git a/src/NetteTesterToPHPUnit/Rector/StaticCall/NetteAssertToPHPUnitAssertRector.php b/src/NetteTesterToPHPUnit/Rector/StaticCall/NetteAssertToPHPUnitAssertRector.php new file mode 100644 index 0000000..bf7c219 --- /dev/null +++ b/src/NetteTesterToPHPUnit/Rector/StaticCall/NetteAssertToPHPUnitAssertRector.php @@ -0,0 +1,74 @@ +assertManipulator = $assertManipulator; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Migrate Nette/Assert calls to PHPUnit', [ + new CodeSample( + <<<'CODE_SAMPLE' +use Tester\Assert; + +function someStaticFunctions() +{ + Assert::true(10 == 5); +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Tester\Assert; + +function someStaticFunctions() +{ + \PHPUnit\Framework\Assert::assertTrue(10 == 5); +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [StaticCall::class]; + } + + /** + * @param StaticCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node->class, new ObjectType('Tester\Assert'))) { + return null; + } + + return $this->assertManipulator->processStaticCall($node); + } +} diff --git a/src/NetteToSymfony/Collector/OnFormVariableMethodCallsCollector.php b/src/NetteToSymfony/Collector/OnFormVariableMethodCallsCollector.php new file mode 100644 index 0000000..1ebeb07 --- /dev/null +++ b/src/NetteToSymfony/Collector/OnFormVariableMethodCallsCollector.php @@ -0,0 +1,114 @@ +simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->nodeTypeResolver = $nodeTypeResolver; + $this->nodeComparator = $nodeComparator; + } + + /** + * @return MethodCall[] + */ + public function collectFromClassMethod(ClassMethod $classMethod): array + { + $newFormVariable = $this->resolveNewFormVariable($classMethod); + if (! $newFormVariable instanceof Expr) { + return []; + } + + return $this->collectOnFormVariableMethodCalls($classMethod, $newFormVariable); + } + + /** + * Matches: + * $form = new Form; + */ + private function resolveNewFormVariable(ClassMethod $classMethod): ?Expr + { + $newFormVariable = null; + + $this->simpleCallableNodeTraverser->traverseNodesWithCallable( + (array) $classMethod->getStmts(), + function (Node $node) use (&$newFormVariable): ?int { + if (! $node instanceof Assign) { + return null; + } + + if (! $this->nodeTypeResolver->isObjectType( + $node->expr, + new ObjectType('Nette\Application\UI\Form') + )) { + return null; + } + + $newFormVariable = $node->var; + + return NodeTraverser::STOP_TRAVERSAL; + } + ); + + return $newFormVariable; + } + + /** + * @return MethodCall[] + */ + private function collectOnFormVariableMethodCalls(ClassMethod $classMethod, Expr $expr): array + { + $onFormVariableMethodCalls = []; + $this->simpleCallableNodeTraverser->traverseNodesWithCallable( + (array) $classMethod->getStmts(), + function (Node $node) use ($expr, &$onFormVariableMethodCalls) { + if (! $node instanceof MethodCall) { + return null; + } + + if (! $this->nodeComparator->areNodesEqual($node->var, $expr)) { + return null; + } + + $onFormVariableMethodCalls[] = $node; + + return null; + } + ); + + return $onFormVariableMethodCalls; + } +} diff --git a/src/NetteToSymfony/Event/EventInfosFactory.php b/src/NetteToSymfony/Event/EventInfosFactory.php new file mode 100644 index 0000000..1f38be9 --- /dev/null +++ b/src/NetteToSymfony/Event/EventInfosFactory.php @@ -0,0 +1,82 @@ + + */ + private $conditionalAssigns = []; + + /** + * @var SimpleCallableNodeTraverser + */ + private $simpleCallableNodeTraverser; + + /** + * @var NodeNameResolver + */ + private $nodeNameResolver; + + /** + * @var Expr[] + */ + private $templateFileExprs = []; + + /** + * @var ScopeNestingComparator + */ + private $scopeNestingComparator; + + /** + * @var BetterNodeFinder + */ + private $betterNodeFinder; + + /** + * @var ThisTemplatePropertyFetchAnalyzer + */ + private $thisTemplatePropertyFetchAnalyzer; + + /** + * @var Return_|null + */ + private $lastReturn; + + /** + * @var ReturnAnalyzer + */ + private $returnAnalyzer; + + public function __construct( + SimpleCallableNodeTraverser $simpleCallableNodeTraverser, + NodeNameResolver $nodeNameResolver, + ScopeNestingComparator $scopeNestingComparator, + BetterNodeFinder $betterNodeFinder, + ThisTemplatePropertyFetchAnalyzer $thisTemplatePropertyFetchAnalyzer, + ReturnAnalyzer $returnAnalyzer + ) { + $this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser; + $this->nodeNameResolver = $nodeNameResolver; + $this->scopeNestingComparator = $scopeNestingComparator; + $this->betterNodeFinder = $betterNodeFinder; + $this->thisTemplatePropertyFetchAnalyzer = $thisTemplatePropertyFetchAnalyzer; + $this->returnAnalyzer = $returnAnalyzer; + } + + public function collectFromClassMethod(ClassMethod $classMethod): ClassMethodRender + { + $this->templateFileExprs = []; + $this->templateVariables = []; + $this->nodesToRemove = []; + $this->conditionalAssigns = []; + + $this->lastReturn = $this->returnAnalyzer->findLastClassMethodReturn($classMethod); + + $this->simpleCallableNodeTraverser->traverseNodesWithCallable( + (array) $classMethod->stmts, + function (Node $node): void { + if ($node instanceof MethodCall) { + $this->collectTemplateFileExpr($node); + } + + if ($node instanceof Assign) { + $this->collectVariableFromAssign($node); + } + } + ); + + return new ClassMethodRender( + $this->templateFileExprs, + $this->templateVariables, + $this->nodesToRemove, + $this->conditionalAssigns + ); + } + + private function collectTemplateFileExpr(MethodCall $methodCall): void + { + if (! $this->nodeNameResolver->isNames($methodCall->name, ['render', 'setFile'])) { + return; + } + + $this->nodesToRemove[] = $methodCall; + if (! isset($methodCall->args[0])) { + return; + } + + $this->templateFileExprs[] = $methodCall->args[0]->value; + } + + private function collectVariableFromAssign(Assign $assign): void + { + // $this->template = x + if ($assign->var instanceof PropertyFetch) { + $propertyFetch = $assign->var; + + if (! $this->thisTemplatePropertyFetchAnalyzer->isTemplatePropertyFetch($propertyFetch->var)) { + return; + } + + $variableName = $this->nodeNameResolver->getName($propertyFetch); + + $foundParent = $this->betterNodeFinder->findParentTypes( + $propertyFetch->var, + ControlStructure::CONDITIONAL_NODE_SCOPE_TYPES + [FunctionLike::class] + ); + + if ($foundParent && $this->scopeNestingComparator->isInBothIfElseBranch( + $foundParent, + $propertyFetch + )) { + $this->conditionalAssigns[$variableName][] = $assign; + return; + } + + if ($foundParent instanceof If_) { + return; + } + + if ($foundParent instanceof Else_) { + return; + } + + // there is a return before this assign, to do not remove it and keep ti + if (! $this->returnAnalyzer->isBeforeLastReturn($assign, $this->lastReturn)) { + return; + } + + $this->templateVariables[$variableName] = $assign->expr; + $this->nodesToRemove[] = $assign; + return; + } + + // $x = $this->template + if (! $this->thisTemplatePropertyFetchAnalyzer->isTemplatePropertyFetch($assign->expr)) { + return; + } + + $this->nodesToRemove[] = $assign; + } +} diff --git a/src/NetteToSymfony/NodeAnalyzer/NetteControlFactoryInterfaceAnalyzer.php b/src/NetteToSymfony/NodeAnalyzer/NetteControlFactoryInterfaceAnalyzer.php new file mode 100644 index 0000000..27956cb --- /dev/null +++ b/src/NetteToSymfony/NodeAnalyzer/NetteControlFactoryInterfaceAnalyzer.php @@ -0,0 +1,53 @@ +returnTypeInferer = $returnTypeInferer; + $this->nodeTypeResolver = $nodeTypeResolver; + } + + /** + * @see https://doc.nette.org/en/3.0/components#toc-components-with-dependencies + */ + public function isComponentFactoryInterface(Interface_ $interface): bool + { + foreach ($interface->getMethods() as $classMethod) { + $returnType = $this->returnTypeInferer->inferFunctionLike($classMethod); + if (! $returnType instanceof TypeWithClassName) { + return false; + } + + $className = $this->nodeTypeResolver->getFullyQualifiedClassName($returnType); + if (is_a($className, 'Nette\Application\UI\Control', true)) { + return true; + } + + if (is_a($className, 'Nette\Application\UI\Form', true)) { + return true; + } + } + + return false; + } +} diff --git a/src/NetteToSymfony/NodeFactory/ActionWithFormProcessClassMethodFactory.php b/src/NetteToSymfony/NodeFactory/ActionWithFormProcessClassMethodFactory.php new file mode 100644 index 0000000..fa2c8c4 --- /dev/null +++ b/src/NetteToSymfony/NodeFactory/ActionWithFormProcessClassMethodFactory.php @@ -0,0 +1,71 @@ +nodeFactory = $nodeFactory; + } + + public function create(string $formTypeClass): ClassMethod + { + $classMethod = $this->nodeFactory->createPublicMethod('actionSomeForm'); + + $requestVariable = new Variable('request'); + $classMethod->params[] = new Param($requestVariable, null, new FullyQualified( + 'Symfony\Component\HttpFoundation\Request' + )); + $classMethod->returnType = new FullyQualified('Symfony\Component\HttpFoundation\Response'); + + $formVariable = new Variable('form'); + + $assign = $this->createFormInstanceAssign($formTypeClass, $formVariable); + $classMethod->stmts[] = new Expression($assign); + + $handleRequestMethodCall = new MethodCall($formVariable, 'handleRequest', [new Arg($requestVariable)]); + $classMethod->stmts[] = new Expression($handleRequestMethodCall); + + $booleanAnd = $this->createFormIsSuccessAndIsValid($formVariable); + $classMethod->stmts[] = new If_($booleanAnd); + + return $classMethod; + } + + private function createFormInstanceAssign(string $formTypeClass, Variable $formVariable): Assign + { + $classConstFetch = $this->nodeFactory->createClassConstReference($formTypeClass); + $args = [new Arg($classConstFetch)]; + $createFormMethodCall = new MethodCall(new Variable('this'), 'createForm', $args); + + return new Assign($formVariable, $createFormMethodCall); + } + + private function createFormIsSuccessAndIsValid(Variable $formVariable): BooleanAnd + { + $isSuccessMethodCall = new MethodCall($formVariable, 'isSuccess'); + $isValidMethodCall = new MethodCall($formVariable, 'isValid'); + + return new BooleanAnd($isSuccessMethodCall, $isValidMethodCall); + } +} diff --git a/src/NetteToSymfony/NodeFactory/BuildFormClassMethodFactory.php b/src/NetteToSymfony/NodeFactory/BuildFormClassMethodFactory.php new file mode 100644 index 0000000..e01cc5a --- /dev/null +++ b/src/NetteToSymfony/NodeFactory/BuildFormClassMethodFactory.php @@ -0,0 +1,36 @@ +nodeFactory = $nodeFactory; + } + + public function create(Variable $formBuilderVariable): ClassMethod + { + $buildFormClassMethod = $this->nodeFactory->createPublicMethod('buildForm'); + $buildFormClassMethod->params[] = new Param($formBuilderVariable, null, new FullyQualified( + 'Symfony\Component\Form\FormBuilderInterface' + )); + $buildFormClassMethod->params[] = new Param(new Variable('options'), null, new Identifier('array')); + + return $buildFormClassMethod; + } +} diff --git a/src/NetteToSymfony/NodeFactory/SymfonyControllerFactory.php b/src/NetteToSymfony/NodeFactory/SymfonyControllerFactory.php new file mode 100644 index 0000000..7759510 --- /dev/null +++ b/src/NetteToSymfony/NodeFactory/SymfonyControllerFactory.php @@ -0,0 +1,64 @@ +nodeNameResolver = $nodeNameResolver; + $this->actionWithFormProcessClassMethodFactory = $actionWithFormProcessClassMethodFactory; + } + + public function createNamespace(Class_ $node, Class_ $formTypeClass): ?Namespace_ + { + $fileInfo = $node->getAttribute(AttributeKey::FILE_INFO); + if (! $fileInfo instanceof SmartFileInfo) { + return null; + } + + $scope = $node->getAttribute(AttributeKey::SCOPE); + if (! $scope instanceof Scope) { + return null; + } + + /** @var string $namespaceName */ + $namespaceName = $scope->getNamespace(); + + $formControllerClass = new Class_('SomeFormController'); + $formControllerClass->extends = new FullyQualified( + 'Symfony\Bundle\FrameworkBundle\Controller\AbstractController' + ); + + $formTypeClass = $namespaceName . '\\' . $this->nodeNameResolver->getName($formTypeClass); + $formControllerClass->stmts[] = $this->actionWithFormProcessClassMethodFactory->create($formTypeClass); + + $namespace = new Namespace_(new Name($namespaceName)); + $namespace->stmts[] = $formControllerClass; + + return $namespace; + } +} diff --git a/src/NetteToSymfony/NodeFactory/SymfonyMethodCallsFactory.php b/src/NetteToSymfony/NodeFactory/SymfonyMethodCallsFactory.php new file mode 100644 index 0000000..5172e07 --- /dev/null +++ b/src/NetteToSymfony/NodeFactory/SymfonyMethodCallsFactory.php @@ -0,0 +1,83 @@ +nodeNameResolver = $nodeNameResolver; + $this->nodeFactory = $nodeFactory; + } + + /** + * @param MethodCall[] $onFormVariableMethodCalls + * @return Expression[] + */ + public function create(array $onFormVariableMethodCalls, Variable $formBuilderVariable): array + { + $symfonyMethodCalls = []; + + // create symfony form from nette form method calls + foreach ($onFormVariableMethodCalls as $onFormVariableMethodCall) { + if (! $this->nodeNameResolver->isName($onFormVariableMethodCall->name, 'addText')) { + continue; + } + // text input + $inputName = $onFormVariableMethodCall->args[0]; + $formTypeClassConstant = $this->nodeFactory->createClassConstReference(TextType::class); + + $args = $this->createAddTextArgs($inputName, $formTypeClassConstant, $onFormVariableMethodCall); + $methodCall = new MethodCall($formBuilderVariable, 'add', $args); + + $symfonyMethodCalls[] = new Expression($methodCall); + } + + return $symfonyMethodCalls; + } + + /** + * @return Arg[] + */ + private function createAddTextArgs( + Arg $arg, + ClassConstFetch $classConstFetch, + MethodCall $onFormVariableMethodCall + ): array { + $args = [$arg, new Arg($classConstFetch)]; + + if (isset($onFormVariableMethodCall->args[1])) { + $optionsArray = new Array_([ + new ArrayItem($onFormVariableMethodCall->args[1]->value, new String_('label')), + ]); + + $args[] = new Arg($optionsArray); + } + + return $args; + } +} diff --git a/src/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php b/src/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php new file mode 100644 index 0000000..974f708 --- /dev/null +++ b/src/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector.php @@ -0,0 +1,215 @@ +symfonyClassConstWithAliases = $eventInfosFactory->create(); + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Changes event names from Nette ones to Symfony ones', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +final class SomeClass implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return ['nette.application' => 'someMethod']; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +final class SomeClass implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return [\SymfonyEvents::KERNEL => 'someMethod']; + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + $classLike = $node->getAttribute(AttributeKey::CLASS_NODE); + if (! $classLike instanceof ClassLike) { + return null; + } + + if (! $this->isObjectType( + $classLike, + new ObjectType('Symfony\Component\EventDispatcher\EventSubscriberInterface') + )) { + return null; + } + + if (! $this->isName($node, 'getSubscribedEvents')) { + return null; + } + + /** @var Return_[] $returnNodes */ + $returnNodes = $this->betterNodeFinder->findInstanceOf($node, Return_::class); + + foreach ($returnNodes as $returnNode) { + if (! $returnNode->expr instanceof Array_) { + continue; + } + + $this->renameArrayKeys($returnNode); + } + + return $node; + } + + private function renameArrayKeys(Return_ $return): void + { + if (! $return->expr instanceof Array_) { + return; + } + + foreach ($return->expr->items as $arrayItem) { + if ($arrayItem === null) { + continue; + } + + $eventInfo = $this->matchStringKeys($arrayItem); + if (! $eventInfo instanceof EventInfo) { + $eventInfo = $this->matchClassConstKeys($arrayItem); + } + + if (! $eventInfo instanceof EventInfo) { + continue; + } + + $arrayItem->key = new ClassConstFetch(new FullyQualified( + $eventInfo->getClass() + ), $eventInfo->getConstant()); + + // method name + $className = (string) $return->getAttribute(AttributeKey::CLASS_NAME); + $methodName = (string) $this->valueResolver->getValue($arrayItem->value); + $this->processMethodArgument($className, $methodName, $eventInfo); + } + } + + private function matchStringKeys(ArrayItem $arrayItem): ?EventInfo + { + if (! $arrayItem->key instanceof String_) { + return null; + } + + foreach ($this->symfonyClassConstWithAliases as $symfonyClassConstWithAlias) { + foreach ($symfonyClassConstWithAlias->getOldStringAliases() as $netteStringName) { + if ($this->valueResolver->isValue($arrayItem->key, $netteStringName)) { + return $symfonyClassConstWithAlias; + } + } + } + + return null; + } + + private function matchClassConstKeys(ArrayItem $arrayItem): ?EventInfo + { + if (! $arrayItem->key instanceof ClassConstFetch) { + return null; + } + + foreach ($this->symfonyClassConstWithAliases as $symfonyClassConstWithAlias) { + $isMatch = $this->resolveClassConstAliasMatch($arrayItem, $symfonyClassConstWithAlias); + if ($isMatch) { + return $symfonyClassConstWithAlias; + } + } + + return null; + } + + private function processMethodArgument(string $class, string $method, EventInfo $eventInfo): void + { + $classMethodNode = $this->nodeRepository->findClassMethod($class, $method); + if (! $classMethodNode instanceof ClassMethod) { + return; + } + + if (count($classMethodNode->params) !== 1) { + return; + } + + $classMethodNode->params[0]->type = new FullyQualified($eventInfo->getEventClass()); + } + + private function resolveClassConstAliasMatch(ArrayItem $arrayItem, EventInfo $eventInfo): bool + { + $classConstFetchNode = $arrayItem->key; + if (! $classConstFetchNode instanceof Expr) { + return false; + } + + foreach ($eventInfo->getOldClassConstAliases() as $netteClassConst) { + if ($this->isName($classConstFetchNode, $netteClassConst)) { + return true; + } + } + + return false; + } +} diff --git a/src/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php b/src/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php new file mode 100644 index 0000000..1a95b96 --- /dev/null +++ b/src/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php @@ -0,0 +1,331 @@ +.*?$)#sm'; + + /** + * @var RouteInfoFactory + */ + private $routeInfoFactory; + + /** + * @var ReturnTypeInferer + */ + private $returnTypeInferer; + + /** + * @var ExplicitRouteAnnotationDecorator + */ + private $explicitRouteAnnotationDecorator; + + /** + * @var SymfonyRouteTagValueNodeFactory + */ + private $symfonyRouteTagValueNodeFactory; + + /** + * @var ObjectType[] + */ + private $routerObjectTypes = []; + + /** + * @var ObjectType + */ + private $routeListObjectType; + + public function __construct( + ExplicitRouteAnnotationDecorator $explicitRouteAnnotationDecorator, + ReturnTypeInferer $returnTypeInferer, + RouteInfoFactory $routeInfoFactory, + SymfonyRouteTagValueNodeFactory $symfonyRouteTagValueNodeFactory + ) { + $this->routeInfoFactory = $routeInfoFactory; + $this->returnTypeInferer = $returnTypeInferer; + $this->explicitRouteAnnotationDecorator = $explicitRouteAnnotationDecorator; + $this->symfonyRouteTagValueNodeFactory = $symfonyRouteTagValueNodeFactory; + + $this->routerObjectTypes = [ + new ObjectType('Nette\Application\IRouter'), + new ObjectType('Nette\Routing\Router'), + ]; + + $this->routeListObjectType = new ObjectType('Nette\Application\Routers\RouteList'); + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Change new Route() from RouteFactory to @Route annotation above controller method', + [ + new CodeSample( + <<<'CODE_SAMPLE' +final class RouterFactory +{ + public function create(): RouteList + { + $routeList = new RouteList(); + $routeList[] = new Route('some-path', SomePresenter::class); + + return $routeList; + } +} + +final class SomePresenter +{ + public function run() + { + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +final class RouterFactory +{ + public function create(): RouteList + { + $routeList = new RouteList(); + + // case of single action controller, usually get() or __invoke() method + $routeList[] = new Route('some-path', SomePresenter::class); + + return $routeList; + } +} + +use Symfony\Component\Routing\Annotation\Route; + +final class SomePresenter +{ + /** + * @Route(path="some-path") + */ + public function run() + { + } +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * List of nodes this class checks, classes that implement @see \PhpParser\Node + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if ($node->stmts === []) { + return null; + } + + $inferedReturnType = $this->returnTypeInferer->inferFunctionLike($node); + if (! $inferedReturnType->isSuperTypeOf($this->routeListObjectType)->yes()) { + return null; + } + + $assignNodes = $this->resolveAssignRouteNodes($node); + if ($assignNodes === []) { + return null; + } + + $routeInfos = $this->createRouteInfosFromAssignNodes($assignNodes); + + /** @var RouteInfo $routeInfo */ + foreach ($routeInfos as $routeInfo) { + $classMethod = $this->resolveControllerClassMethod($routeInfo); + if (! $classMethod instanceof ClassMethod) { + continue; + } + + $symfonyRoutePhpDocTagValueNode = $this->createSymfonyRoutePhpDocTagValueNode($routeInfo); + + $this->explicitRouteAnnotationDecorator->decorateClassMethodWithRouteAnnotation( + $classMethod, + $symfonyRoutePhpDocTagValueNode + ); + } + + // complete all other non-explicit methods, from "/" + $this->completeImplicitRoutes(); + + // remove routes + $this->removeNodes($assignNodes); + + return null; + } + + /** + * @return Assign[] + */ + private function resolveAssignRouteNodes(ClassMethod $classMethod): array + { + // look for <...>[] = IRoute + + return $this->betterNodeFinder->find((array) $classMethod->stmts, function (Node $node): bool { + if (! $node instanceof Assign) { + return false; + } + + // $routeList[] = + if (! $node->var instanceof ArrayDimFetch) { + return false; + } + + if ($this->nodeTypeResolver->isObjectTypes($node->expr, $this->routerObjectTypes)) { + return true; + } + + if ($node->expr instanceof StaticCall) { + // for custom static route factories + return $this->nodeTypeResolver->isObjectType($node->expr, new ObjectType('Nette\Application\IRouter')); + } + + return false; + }); + } + + /** + * @param Assign[] $assignNodes + * @return RouteInfo[] + */ + private function createRouteInfosFromAssignNodes(array $assignNodes): array + { + $routeInfos = []; + + // collect annotations and target controllers + foreach ($assignNodes as $assignNode) { + $routeNameToControllerMethod = $this->routeInfoFactory->createFromNode($assignNode->expr); + if (! $routeNameToControllerMethod instanceof RouteInfo) { + continue; + } + + $routeInfos[] = $routeNameToControllerMethod; + } + + return $routeInfos; + } + + private function resolveControllerClassMethod(RouteInfo $routeInfo): ?ClassMethod + { + $classNode = $this->nodeRepository->findClass($routeInfo->getClass()); + if (! $classNode instanceof Class_) { + return null; + } + + return $classNode->getMethod($routeInfo->getMethod()); + } + + private function createSymfonyRoutePhpDocTagValueNode(RouteInfo $routeInfo): SymfonyRouteTagValueNode + { + return $this->symfonyRouteTagValueNodeFactory->createFromItems([ + 'path' => $routeInfo->getPath(), + 'methods' => $routeInfo->getHttpMethods(), + ]); + } + + private function completeImplicitRoutes(): void + { + $presenterClasses = $this->nodeRepository->findClassesBySuffix('Presenter'); + + foreach ($presenterClasses as $presenterClass) { + foreach ($presenterClass->getMethods() as $classMethod) { + if ($this->shouldSkipClassMethod($classMethod)) { + continue; + } + + $path = $this->resolvePathFromClassAndMethodNodes($presenterClass, $classMethod); + $symfonyRoutePhpDocTagValueNode = $this->symfonyRouteTagValueNodeFactory->createFromItems([ + 'path' => $path, + ]); + + $this->explicitRouteAnnotationDecorator->decorateClassMethodWithRouteAnnotation( + $classMethod, + $symfonyRoutePhpDocTagValueNode + ); + } + } + } + + private function shouldSkipClassMethod(ClassMethod $classMethod): bool + { + // not an action method + if (! $classMethod->isPublic()) { + return true; + } + + if (! $this->isName($classMethod, '#^(render|action)#')) { + return true; + } + $hasRouteAnnotation = $classMethod->getAttribute(ExplicitRouteAnnotationDecorator::HAS_ROUTE_ANNOTATION); + + if ($hasRouteAnnotation) { + return true; + } + + // already has Route tag + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); + return $phpDocInfo->hasByType(SymfonyRouteTagValueNode::class); + } + + private function resolvePathFromClassAndMethodNodes(Class_ $class, ClassMethod $classMethod): string + { + /** @var string $presenterName */ + $presenterName = $this->getName($class); + + /** @var string $presenterPart */ + $presenterPart = Strings::after($presenterName, '\\', -1); + + $presenterPart = Strings::substring($presenterPart, 0, -Strings::length('Presenter')); + + $stringy = new Stringy($presenterPart); + $presenterPart = (string) $stringy->dasherize(); + + $match = (array) Strings::match($this->getName($classMethod), self::ACTION_RENDER_NAME_MATCHING_REGEX); + $actionPart = lcfirst($match['short_action_name']); + + return $presenterPart . '/' . $actionPart; + } +} diff --git a/src/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector.php b/src/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector.php new file mode 100644 index 0000000..e78f069 --- /dev/null +++ b/src/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector.php @@ -0,0 +1,205 @@ +onFormVariableMethodCallsCollector = $onFormVariableMethodCallsCollector; + $this->symfonyControllerFactory = $symfonyControllerFactory; + $this->buildFormClassMethodFactory = $buildFormClassMethodFactory; + $this->symfonyMethodCallsFactory = $symfonyMethodCallsFactory; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Change Form that extends Control to Controller and decoupled FormType', [ + new ExtraFileCodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Form; +use Nette\Application\UI\Control; + +class SomeForm extends Control +{ + public function createComponentForm() + { + $form = new Form(); + $form->addText('name', 'Your name'); + + $form->onSuccess[] = [$this, 'processForm']; + } + + public function processForm(Form $form) + { + // process me + } +} +CODE_SAMPLE +, + <<<'CODE_SAMPLE' +class SomeFormController extends \Symfony\Bundle\FrameworkBundle\Controller\AbstractController +{ + /** + * @Route(...) + */ + public function actionSomeForm(\Symfony\Component\HttpFoundation\Request $request): \Symfony\Component\HttpFoundation\Response + { + $form = $this->createForm(SomeFormType::class); + $form->handleRequest($request); + + if ($form->isSuccess() && $form->isValid()) { + // process me + } + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +add('name', TextType::class, [ + 'label' => 'Your name' + ]); + } +} +CODE_SAMPLE + ), + + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node, new ObjectType('Nette\Application\UI\Control'))) { + return null; + } + + foreach ($node->getMethods() as $classMethod) { + if (! $this->isName($classMethod->name, 'createComponent*')) { + continue; + } + + $formTypeClass = $this->collectFormMethodCallsAndCreateFormTypeClass($classMethod); + if (! $formTypeClass instanceof Class_) { + continue; + } + + $symfonyControllerNamespace = $this->symfonyControllerFactory->createNamespace($node, $formTypeClass); + if (! $symfonyControllerNamespace instanceof Namespace_) { + continue; + } + + $addedFileWithNodes = new AddedFileWithNodes('src/Controller/SomeFormController.php', [ + $symfonyControllerNamespace, + ]); + $this->removedAndAddedFilesCollector->addAddedFile($addedFileWithNodes); + + return $formTypeClass; + } + + return $node; + } + + private function collectFormMethodCallsAndCreateFormTypeClass(ClassMethod $classMethod): ?Class_ + { + $onFormVariableMethodCalls = $this->onFormVariableMethodCallsCollector->collectFromClassMethod( + $classMethod + ); + + if ($onFormVariableMethodCalls === []) { + return null; + } + + $formBuilderVariable = new Variable('formBuilder'); + + // public function buildForm(\Symfony\Component\Form\FormBuilderInterface $formBuilder, array $options) + $buildFormClassMethod = $this->buildFormClassMethodFactory->create($formBuilderVariable); + + $symfonyMethodCalls = $this->symfonyMethodCallsFactory->create( + $onFormVariableMethodCalls, + $formBuilderVariable + ); + $buildFormClassMethod->stmts = $symfonyMethodCalls; + + return $this->createFormTypeClassFromBuildFormClassMethod($buildFormClassMethod); + } + + private function createFormTypeClassFromBuildFormClassMethod(ClassMethod $buildFormClassMethod): Class_ + { + $formTypeClass = new Class_('SomeFormType'); + $formTypeClass->extends = new FullyQualified('Symfony\Component\Form\AbstractType'); + + $formTypeClass->stmts[] = $buildFormClassMethod; + + return $formTypeClass; + } +} diff --git a/src/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector.php b/src/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector.php new file mode 100644 index 0000000..7907fb9 --- /dev/null +++ b/src/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector.php @@ -0,0 +1,190 @@ +actionRenderFactory = $actionRenderFactory; + $this->netteClassAnalyzer = $netteClassAnalyzer; + $this->classNaming = $classNaming; + $this->classMethodRenderAnalyzer = $classMethodRenderAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Migrate Nette Component to Symfony Controller', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI\Control; + +class SomeControl extends Control +{ + public function render() + { + $this->template->param = 'some value'; + $this->template->render(__DIR__ . '/poll.latte'); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; + +class SomeController extends AbstractController +{ + public function some(): Response + { + return $this->render(__DIR__ . '/poll.latte', ['param' => 'some value']); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->netteClassAnalyzer->isInComponent($node)) { + return null; + } + + $shortClassName = $this->nodeNameResolver->getShortName($node); + $shortClassName = $this->classNaming->replaceSuffix($shortClassName, 'Control', 'Controller'); + + $node->name = new Identifier($shortClassName); + + $node->extends = new FullyQualified('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'); + + $classMethod = $node->getMethod('render'); + if ($classMethod !== null) { + $this->processRenderMethod($classMethod); + } + + return $node; + } + + private function processRenderMethod(ClassMethod $classMethod): void + { + $this->processGetPresenterGetSessionMethodCall($classMethod); + + $classMethod->name = new Identifier('action'); + + $classMethodRender = $this->classMethodRenderAnalyzer->collectFromClassMethod($classMethod); + $methodCall = $this->actionRenderFactory->createThisRenderMethodCall($classMethodRender); + + // add return in the end + $return = new Return_($methodCall); + $classMethod->stmts[] = $return; + + if ($this->isAtLeastPhpVersion(PhpVersionFeature::SCALAR_TYPES)) { + $classMethod->returnType = new FullyQualified('Symfony\Component\HttpFoundation\Response'); + } + + $this->removeNodes($classMethodRender->getNodesToRemove()); + } + + private function processGetPresenterGetSessionMethodCall(ClassMethod $classMethod): void + { + $this->traverseNodesWithCallable((array) $classMethod->getStmts(), function (Node $node): ?MethodCall { + if (! $node instanceof MethodCall) { + return null; + } + + if (! $this->isName($node->name, 'getSession')) { + return null; + } + + if (! $node->var instanceof MethodCall) { + return null; + } + + if (! $this->isName($node->var->name, 'getPresenter')) { + return null; + } + + $node->var = new PropertyFetch(new Variable('this'), 'session'); + + $classLike = $node->getAttribute(AttributeKey::CLASS_NODE); + if (! $classLike instanceof Class_) { + throw new ShouldNotHappenException(); + } + + $this->addConstructorDependencyToClass( + $classLike, + new FullyQualifiedObjectType('Nette\Http\Session'), + 'session' + ); + + return $node; + }); + } +} diff --git a/src/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector.php b/src/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector.php new file mode 100644 index 0000000..e51ad86 --- /dev/null +++ b/src/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector.php @@ -0,0 +1,75 @@ +netteControlFactoryInterfaceAnalyzer = $netteControlFactoryInterfaceAnalyzer; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Interface factories are not needed in Symfony. Clear constructor injection is used instead', [ + new CodeSample( + <<<'CODE_SAMPLE' +interface SomeControlFactoryInterface +{ + public function create(); +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Interface_::class]; + } + + /** + * @param Interface_ $node + */ + public function refactor(Node $node): ?Node + { + $smartFileInfo = $node->getAttribute(SmartFileInfo::class); + if ($smartFileInfo === null) { + return null; + } + + if (! $this->netteControlFactoryInterfaceAnalyzer->isComponentFactoryInterface($node)) { + return null; + } + + $this->removedAndAddedFilesCollector->removeFile($smartFileInfo); + + return null; + } +} diff --git a/src/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector.php b/src/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector.php new file mode 100644 index 0000000..257f9e3 --- /dev/null +++ b/src/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector.php @@ -0,0 +1,103 @@ +classMethodManipulator = $classMethodManipulator; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Changes getHeader() to $request->headers->get()', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Request; + +final class SomeController +{ + public static function someAction(Request $request) + { + $header = $this->httpRequest->getHeader('x'); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Nette\Request; + +final class SomeController +{ + public static function someAction(Request $request) + { + $header = $request->headers->get('x'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node->var, new ObjectType('Nette\Http\Request'))) { + return null; + } + + if (! $this->isName($node->name, 'getHeader')) { + return null; + } + + $requestName = $this->classMethodManipulator->addMethodParameterIfMissing( + $node, + new ObjectType('Symfony\Component\HttpFoundation\Request'), + ['request', 'symfonyRequest'] + ); + + $variable = new Variable($requestName); + $headersPropertyFetch = new PropertyFetch($variable, 'headers'); + + $node->var = $headersPropertyFetch; + $node->name = new Identifier('get'); + + return $node; + } +} diff --git a/src/NetteToSymfony/Rector/MethodCall/FromRequestGetParameterToAttributesGetRector.php b/src/NetteToSymfony/Rector/MethodCall/FromRequestGetParameterToAttributesGetRector.php new file mode 100644 index 0000000..4ff165a --- /dev/null +++ b/src/NetteToSymfony/Rector/MethodCall/FromRequestGetParameterToAttributesGetRector.php @@ -0,0 +1,85 @@ +get()" from Nette to Symfony', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Request; + +final class SomeController +{ + public static function someAction(Request $request) + { + $value = $request->getParameter('abz'); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Nette\Request; + +final class SomeController +{ + public static function someAction(Request $request) + { + $value = $request->attribute->get('abz'); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType($node->var, new ObjectType('Nette\Application\Request'))) { + return null; + } + + if (! $this->isName($node->name, 'getParameter')) { + return null; + } + + $requestAttributesPropertyFetch = new PropertyFetch($node->var, 'attributes'); + $node->var = $requestAttributesPropertyFetch; + + $node->name = new Identifier('get'); + + return $node; + } +} diff --git a/src/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector.php b/src/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector.php new file mode 100644 index 0000000..5988685 --- /dev/null +++ b/src/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector.php @@ -0,0 +1,216 @@ + + */ + private const ADD_METHOD_TO_FORM_TYPE = [ + 'addText' => 'Symfony\Component\Form\Extension\Core\Type\TextType', + 'addPassword' => 'Symfony\Component\Form\Extension\Core\Type\PasswordType', + 'addTextArea' => 'Symfony\Component\Form\Extension\Core\Type\TextareaType', + 'addEmail' => 'Symfony\Component\Form\Extension\Core\Type\EmailType', + 'addInteger' => 'Symfony\Component\Form\Extension\Core\Type\IntegerType', + 'addHidden' => 'Symfony\Component\Form\Extension\Core\Type\HiddenType', + // https://symfony.com/doc/current/reference/forms/types/checkbox.html + 'addCheckbox' => 'Symfony\Component\Form\Extension\Core\Type\CheckboxType', + 'addUpload' => 'Symfony\Component\Form\Extension\Core\Type\FileType', + 'addImage' => 'Symfony\Component\Form\Extension\Core\Type\FileType', + 'addMultiUpload' => 'Symfony\Component\Form\Extension\Core\Type\FileType', + // https://symfony.com/doc/current/reference/forms/types/choice.html#select-tag-checkboxes-or-radio-buttons + 'addSelect' => 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', + 'addRadioList' => 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', + 'addCheckboxList' => 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', + 'addMultiSelect' => 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', + 'addSubmit' => 'Symfony\Component\Form\Extension\Core\Type\SubmitType', + 'addButton' => 'Symfony\Component\Form\Extension\Core\Type\ButtonType', + ]; + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + 'Migrate Nette\Forms in Presenter to Symfony', + [ + new CodeSample( + <<<'CODE_SAMPLE' +use Nette\Application\UI; + +class SomePresenter extends UI\Presenter +{ + public function someAction() + { + $form = new UI\Form; + $form->addText('name', 'Name:'); + $form->addPassword('password', 'Password:'); + $form->addSubmit('login', 'Sign up'); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Nette\Application\UI; + +class SomePresenter extends UI\Presenter +{ + public function someAction() + { + $form = $this->createFormBuilder(); + $form->add('name', \Symfony\Component\Form\Extension\Core\Type\TextType::class, [ + 'label' => 'Name:' + ]); + $form->add('password', \Symfony\Component\Form\Extension\Core\Type\PasswordType::class, [ + 'label' => 'Password:' + ]); + $form->add('login', \Symfony\Component\Form\Extension\Core\Type\SubmitType::class, [ + 'label' => 'Sign up' + ]); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [New_::class, MethodCall::class]; + } + + /** + * @param New_|MethodCall $node + */ + public function refactor(Node $node): ?Node + { + $classLike = $node->getAttribute(AttributeKey::CLASS_NODE); + if (! $classLike instanceof ClassLike) { + return null; + } + + if (! $this->isObjectType($classLike, new ObjectType('Nette\Application\IPresenter'))) { + return null; + } + + if ($node instanceof New_) { + return $this->processNew($node); + } + + /** @var MethodCall $node */ + if (! $this->isObjectType($node->var, new ObjectType('Nette\Application\UI\Form'))) { + return null; + } + + foreach (self::ADD_METHOD_TO_FORM_TYPE as $method => $classType) { + if (! $this->isName($node->name, $method)) { + continue; + } + + $this->processAddMethod($node, $method, $classType); + } + + return $node; + } + + private function processNew(New_ $new): ?MethodCall + { + if (! $this->isName($new->class, 'Nette\Application\UI\Form')) { + return null; + } + + return $this->nodeFactory->createMethodCall('this', 'createFormBuilder'); + } + + private function processAddMethod(MethodCall $methodCall, string $method, string $classType): void + { + $methodCall->name = new Identifier('add'); + + // remove unused params + if ($method === 'addText') { + unset($methodCall->args[3], $methodCall->args[4]); + } + + // has label + $optionsArray = new Array_(); + if (isset($methodCall->args[1])) { + $optionsArray->items[] = new ArrayItem($methodCall->args[1]->value, new String_('label')); + } + + $this->addChoiceTypeOptions($method, $optionsArray); + $this->addMultiFileTypeOptions($method, $optionsArray); + + $methodCall->args[1] = new Arg($this->nodeFactory->createClassConstReference($classType)); + + if ($optionsArray->items !== []) { + $methodCall->args[2] = new Arg($optionsArray); + } + } + + private function addChoiceTypeOptions(string $method, Array_ $optionsArray): void + { + if ($method === 'addSelect') { + $expanded = false; + $multiple = false; + } elseif ($method === 'addRadioList') { + $expanded = true; + $multiple = false; + } elseif ($method === 'addCheckboxList') { + $expanded = true; + $multiple = true; + } elseif ($method === 'addMultiSelect') { + $expanded = false; + $multiple = true; + } else { + return; + } + + $optionsArray->items[] = new ArrayItem( + $expanded ? $this->nodeFactory->createTrue() : $this->nodeFactory->createFalse(), + new String_('expanded') + ); + + $optionsArray->items[] = new ArrayItem( + $multiple ? $this->nodeFactory->createTrue() : $this->nodeFactory->createFalse(), + new String_('multiple') + ); + } + + private function addMultiFileTypeOptions(string $method, Array_ $optionsArray): void + { + if ($method !== 'addMultiUpload') { + return; + } + + $optionsArray->items[] = new ArrayItem($this->nodeFactory->createTrue(), new String_('multiple')); + } +} diff --git a/src/NetteToSymfony/Rector/MethodCall/WrapTransParameterNameRector.php b/src/NetteToSymfony/Rector/MethodCall/WrapTransParameterNameRector.php new file mode 100644 index 0000000..aad6561 --- /dev/null +++ b/src/NetteToSymfony/Rector/MethodCall/WrapTransParameterNameRector.php @@ -0,0 +1,125 @@ +trans( + 'Hello %name%', + ['name' => $name] + ); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use Symfony\Component\Translation\Translator; + +final class SomeController +{ + public function run() + { + $translator = new Translator(''); + $translated = $translator->trans( + 'Hello %name%', + ['%name%' => $name] + ); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->isObjectType( + $node->var, + new ObjectType('Symfony\Component\Translation\TranslatorInterface') + )) { + return null; + } + + if (! $this->isName($node->name, 'trans')) { + return null; + } + + if (count($node->args) < 2) { + return null; + } + + if (! $node->args[1]->value instanceof Array_) { + return null; + } + + /** @var Array_ $parametersArrayNode */ + $parametersArrayNode = $node->args[1]->value; + + foreach ($parametersArrayNode->items as $arrayItem) { + if ($arrayItem === null) { + continue; + } + + if (! $arrayItem->key instanceof String_) { + continue; + } + + if (Strings::match($arrayItem->key->value, self::BETWEEN_PERCENT_CHARS_REGEX)) { + continue; + } + + $arrayItem->key = new String_('%' . $arrayItem->key->value . '%'); + } + + return $node; + } +} diff --git a/src/NetteToSymfony/Route/RouteInfoFactory.php b/src/NetteToSymfony/Route/RouteInfoFactory.php new file mode 100644 index 0000000..c0181dc --- /dev/null +++ b/src/NetteToSymfony/Route/RouteInfoFactory.php @@ -0,0 +1,208 @@ +nodeNameResolver = $nodeNameResolver; + $this->valueResolver = $valueResolver; + $this->nodeRepository = $nodeRepository; + $this->reflectionProvider = $reflectionProvider; + } + + public function createFromNode(Node $node): ?RouteInfo + { + if ($node instanceof New_) { + if ($this->hasNoArg($node)) { + return null; + } + return $this->createRouteInfoFromArgs($node); + } + + // Route::create() + if ($node instanceof StaticCall) { + if (! isset($node->args[0])) { + return null; + } + if (! isset($node->args[1])) { + return null; + } + if (! $this->nodeNameResolver->isNames($node->name, ['get', 'head', 'post', 'put', 'patch', 'delete'])) { + return null; + } + + /** @var string $methodName */ + $methodName = $this->nodeNameResolver->getName($node->name); + $uppercasedMethodName = strtoupper($methodName); + + $methods = []; + if ($uppercasedMethodName !== null) { + $methods[] = $uppercasedMethodName; + } + + return $this->createRouteInfoFromArgs($node, $methods); + } + + return null; + } + + private function hasNoArg(New_ $new): bool + { + if (! isset($new->args[0])) { + return true; + } + + return ! isset($new->args[1]); + } + + /** + * @param New_|StaticCall $node + * @param string[] $methods + */ + private function createRouteInfoFromArgs(Node $node, array $methods = []): ?RouteInfo + { + $pathArgument = $node->args[0]->value; + $routePath = $this->valueResolver->getValue($pathArgument); + // route path is needed + if ($routePath === null) { + return null; + } + if (! is_string($routePath)) { + return null; + } + + $routePath = $this->normalizeArgumentWrappers($routePath); + + $targetNode = $node->args[1]->value; + if ($targetNode instanceof ClassConstFetch) { + return $this->createForClassConstFetch($node, $methods, $routePath); + } + + if ($targetNode instanceof String_) { + return $this->createForString($targetNode, $routePath); + } + + return null; + } + + private function normalizeArgumentWrappers(string $routePath): string + { + return str_replace(['<', '>'], ['{', '}'], $routePath); + } + + /** + * @param New_|StaticCall $node + * @param string[] $methods + */ + private function createForClassConstFetch(Node $node, array $methods, string $routePath): ?RouteInfo + { + /** @var ClassConstFetch $controllerMethodNode */ + $controllerMethodNode = $node->args[1]->value; + + // SomePresenter::class + if ($this->nodeNameResolver->isName($controllerMethodNode->name, 'class')) { + $presenterClass = $this->nodeNameResolver->getName($controllerMethodNode->class); + if ($presenterClass === null) { + return null; + } + + if (! $this->reflectionProvider->hasClass($presenterClass)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($presenterClass); + if ($classReflection->hasMethod('run')) { + return new RouteInfo($presenterClass, 'run', $routePath, $methods); + } + } + + return null; + } + + private function createForString(String_ $string, string $routePath): ?RouteInfo + { + $targetValue = $string->value; + if (! Strings::contains($targetValue, ':')) { + return null; + } + + [$controller, $method] = explode(':', $targetValue); + + // detect class by controller name? + // foreach all instance and try to match a name $controller . 'Presenter/Controller' + + $class = $this->nodeRepository->findByShortName($controller . 'Presenter'); + if (! $class instanceof Class_) { + $class = $this->nodeRepository->findByShortName($controller . 'Controller'); + } + + // unable to find here + if (! $class instanceof Class_) { + return null; + } + + $controllerClass = $this->nodeNameResolver->getName($class); + if ($controllerClass === null) { + return null; + } + + if (! $this->reflectionProvider->hasClass($controllerClass)) { + return null; + } + + $controllerClassReflection = $this->reflectionProvider->getClass($controllerClass); + + $renderMethodName = 'render' . ucfirst($method); + if ($controllerClassReflection->hasMethod($renderMethodName)) { + return new RouteInfo($controllerClass, $renderMethodName, $routePath, []); + } + + $actionMethodName = 'action' . ucfirst($method); + if ($controllerClassReflection->hasMethod($actionMethodName)) { + return new RouteInfo($controllerClass, $actionMethodName, $routePath, []); + } + + return null; + } +} diff --git a/src/NetteToSymfony/Routing/ExplicitRouteAnnotationDecorator.php b/src/NetteToSymfony/Routing/ExplicitRouteAnnotationDecorator.php new file mode 100644 index 0000000..ee45de1 --- /dev/null +++ b/src/NetteToSymfony/Routing/ExplicitRouteAnnotationDecorator.php @@ -0,0 +1,37 @@ +phpDocInfoFactory = $phpDocInfoFactory; + } + + public function decorateClassMethodWithRouteAnnotation( + ClassMethod $classMethod, + SymfonyRouteTagValueNode $symfonyRouteTagValueNode + ): void { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); + $phpDocInfo->addTagValueNode($symfonyRouteTagValueNode); + + $classMethod->setAttribute(self::HAS_ROUTE_ANNOTATION, true); + } +} diff --git a/src/NetteToSymfony/SymfonyFormAbstractTypeFactory.php b/src/NetteToSymfony/SymfonyFormAbstractTypeFactory.php new file mode 100644 index 0000000..8a85fa4 --- /dev/null +++ b/src/NetteToSymfony/SymfonyFormAbstractTypeFactory.php @@ -0,0 +1,104 @@ +nodeFactory = $nodeFactory; + $this->nodeNameResolver = $nodeNameResolver; + } + + /** + * @api + * @param MethodCall[] $methodCalls + */ + public function createFromNetteFormMethodCalls(array $methodCalls): Class_ + { + $formBuilderVariable = new Variable('formBuilder'); + + // public function buildForm(\Symfony\Component\Form\FormBuilderInterface $formBuilder, array $options) + $buildFormClassMethod = $this->nodeFactory->createPublicMethod('buildForm'); + $buildFormClassMethod->params[] = new Param($formBuilderVariable, null, new FullyQualified( + 'Symfony\Component\Form\FormBuilderInterface' + )); + $buildFormClassMethod->params[] = new Param(new Variable('options'), null, new Identifier('array')); + + $symfonyMethodCalls = $this->createBuildFormMethodCalls($methodCalls, $formBuilderVariable); + + $buildFormClassMethod->stmts = $symfonyMethodCalls; + + $formTypeClass = new Class_('SomeFormType'); + $formTypeClass->extends = new FullyQualified('Symfony\Component\Form\AbstractType'); + + $formTypeClass->stmts[] = $buildFormClassMethod; + + return $formTypeClass; + } + + /** + * @param MethodCall[] $methodCalls + * @return Expression[] + */ + private function createBuildFormMethodCalls(array $methodCalls, Variable $formBuilderVariable): array + { + $buildFormMethodCalls = []; + + // create symfony form from nette form method calls + foreach ($methodCalls as $methodCall) { + if ($this->nodeNameResolver->isName($methodCall->name, 'addText')) { + $optionsArray = $this->createOptionsArray($methodCall); + + $formTypeClassConstant = $this->nodeFactory->createClassConstReference(TextType::class); + + $args = [$methodCall->args[0], new Arg($formTypeClassConstant)]; + + if ($optionsArray instanceof Array_) { + $args[] = new Arg($optionsArray); + } + + $methodCall = new MethodCall($formBuilderVariable, 'add', $args); + $buildFormMethodCalls[] = new Expression($methodCall); + } + } + + return $buildFormMethodCalls; + } + + private function createOptionsArray(MethodCall $methodCall): ?Array_ + { + if (! isset($methodCall->args[1])) { + return null; + } + + return new Array_([new ArrayItem($methodCall->args[1]->value, new String_('label'))]); + } +} diff --git a/src/NetteToSymfony/ValueObject/ClassMethodRender.php b/src/NetteToSymfony/ValueObject/ClassMethodRender.php new file mode 100644 index 0000000..c5ecfa8 --- /dev/null +++ b/src/NetteToSymfony/ValueObject/ClassMethodRender.php @@ -0,0 +1,80 @@ + + */ + private $templateVariables = []; + + /** + * @var array + */ + private $conditionalAssigns = []; + + /** + * @var Expr[] + */ + private $templateFileExprs = []; + + /** + * @param Expr[] $templateFileExprs + * @param array $templateVariables + * @param Node[] $nodesToRemove + * @param array $conditionalAssigns + */ + public function __construct( + array $templateFileExprs, + array $templateVariables, + array $nodesToRemove, + array $conditionalAssigns + ) { + $this->templateVariables = $templateVariables; + $this->nodesToRemove = $nodesToRemove; + $this->conditionalAssigns = $conditionalAssigns; + $this->templateFileExprs = $templateFileExprs; + } + + /** + * @return array + */ + public function getTemplateVariables(): array + { + return $this->templateVariables; + } + + /** + * @return string[] + */ + public function getConditionalVariableNames(): array + { + return array_keys($this->conditionalAssigns); + } + + /** + * @return Node[] + */ + public function getNodesToRemove(): array + { + return $this->nodesToRemove; + } + + public function getFirstTemplateFileExpr(): ?Expr + { + return $this->templateFileExprs[0] ?? null; + } +} diff --git a/src/NetteToSymfony/ValueObject/EventInfo.php b/src/NetteToSymfony/ValueObject/EventInfo.php new file mode 100644 index 0000000..4d9bbbd --- /dev/null +++ b/src/NetteToSymfony/ValueObject/EventInfo.php @@ -0,0 +1,82 @@ +oldStringAliases = $oldStringAliases; + $this->oldClassConstAliases = $oldClassConstAliases; + $this->class = $class; + $this->constant = $constant; + $this->eventClass = $eventClass; + } + + /** + * @return string[] + */ + public function getOldStringAliases(): array + { + return $this->oldStringAliases; + } + + /** + * @return string[] + */ + public function getOldClassConstAliases(): array + { + return $this->oldClassConstAliases; + } + + public function getClass(): string + { + return $this->class; + } + + public function getConstant(): string + { + return $this->constant; + } + + public function getEventClass(): string + { + return $this->eventClass; + } +} diff --git a/src/NetteToSymfony/ValueObject/RouteInfo.php b/src/NetteToSymfony/ValueObject/RouteInfo.php new file mode 100644 index 0000000..17f3495 --- /dev/null +++ b/src/NetteToSymfony/ValueObject/RouteInfo.php @@ -0,0 +1,62 @@ +class = $class; + $this->method = $method; + $this->path = $path; + $this->httpMethods = $httpMethods; + } + + public function getClass(): string + { + return $this->class; + } + + public function getMethod(): string + { + return $this->method; + } + + public function getPath(): string + { + return $this->path; + } + + /** + * @return string[] + */ + public function getHttpMethods(): array + { + return $this->httpMethods; + } +} diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/back_order.php.inc b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/back_order.php.inc new file mode 100644 index 0000000..2c1581c --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/back_order.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_name.php.inc b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_name.php.inc new file mode 100644 index 0000000..8f636e9 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/do_not_remove_future_use_name.php.inc @@ -0,0 +1,44 @@ +customName = $name; + $this->value = $value; + } +} + +?> +----- +customName = $name; + $this->value = $value; + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/fixture.php.inc b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..2fd38a5 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/fixture.php.inc @@ -0,0 +1,34 @@ +value = $value; + } +} + +?> +----- +value = $value; + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/keep_presenter_parent.php.inc b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/keep_presenter_parent.php.inc new file mode 100644 index 0000000..f109056 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/keep_presenter_parent.php.inc @@ -0,0 +1,16 @@ +dependency = $dependency; + } +} diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/new_instance.php.inc b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/new_instance.php.inc new file mode 100644 index 0000000..463fb9f --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/new_instance.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/skip_another_params_in_new_instance.php.inc b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/skip_another_params_in_new_instance.php.inc new file mode 100644 index 0000000..b65dd63 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Fixture/skip_another_params_in_new_instance.php.inc @@ -0,0 +1,15 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return RemoveParentAndNameFromComponentConstructorRector::class; + } +} diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Source/SomeControlWithConstructorParentAndName.php b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Source/SomeControlWithConstructorParentAndName.php new file mode 100644 index 0000000..8b992c4 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Source/SomeControlWithConstructorParentAndName.php @@ -0,0 +1,15 @@ +parent = $parent; + $this->name = $name; + } +} diff --git a/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Source/SomeControlWithoutConstructorParentAndName.php b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Source/SomeControlWithoutConstructorParentAndName.php new file mode 100644 index 0000000..9527b9a --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/RemoveParentAndNameFromComponentConstructorRector/Source/SomeControlWithoutConstructorParentAndName.php @@ -0,0 +1,18 @@ +key = $key; + $this->value = $value; + } +} diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/callaback_return.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/callaback_return.php.inc new file mode 100644 index 0000000..ced67b3 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/callaback_return.php.inc @@ -0,0 +1,43 @@ +template->maybe = 'true'; + + $values = function () { + return 100; + }; + + $this->template->definitely = 'true'; + + $this->template->render(__DIR__ . '/someFile.latte'); + } +} + +?> +----- +template->render(__DIR__ . '/someFile.latte', ['maybe' => 'true', 'definitely' => 'true']); + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/conditional_and_one_normal.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/conditional_and_one_normal.php.inc new file mode 100644 index 0000000..bd5b195 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/conditional_and_one_normal.php.inc @@ -0,0 +1,39 @@ +template->yes = 'true'; + } + + $this->template->normalVariable = 'include me!'; + $this->template->render(__DIR__ . '/someFile.latte'); + } +} + +?> +----- +template->yes = 'true'; + } + $this->template->render(__DIR__ . '/someFile.latte', ['normalVariable' => 'include me!']); + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/double_render.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/double_render.php.inc new file mode 100644 index 0000000..55b645b --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/double_render.php.inc @@ -0,0 +1,44 @@ +template->yes = 'true'; + + if (mt_rand(0, 100)) { + $this->template->render(__DIR__ . '/first.latte'); + } else { + $this->template->render(__DIR__ . '/second.latte'); + } + } +} + +?> +----- +template->render(__DIR__ . '/first.latte', $parameters); + } else { + $this->template->render(__DIR__ . '/second.latte', $parameters); + } + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/render_after_return.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/render_after_return.php.inc new file mode 100644 index 0000000..62bfde4 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/render_after_return.php.inc @@ -0,0 +1,45 @@ +template->maybe = 'true'; + + if (mt_rand(0, 1000)) { + return; + } + + $this->template->definitely = 'true'; + + $this->template->render(__DIR__ . '/someFile.latte'); + } +} + +?> +----- +template->maybe = 'true'; + + if (mt_rand(0, 1000)) { + return; + } + + $this->template->render(__DIR__ . '/someFile.latte', ['definitely' => 'true']); + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/single_conditional_variable.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/single_conditional_variable.php.inc new file mode 100644 index 0000000..ef18bee --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/single_conditional_variable.php.inc @@ -0,0 +1,43 @@ +template->someKey = 'true'; + } else { + $this->template->someKey = 'false'; + } + + $this->template->render(__DIR__ . '/someFile.latte'); + } +} + +?> +----- +template->render(__DIR__ . '/someFile.latte', ['someKey' => $someKey]); + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_abstract_control.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_abstract_control.php.inc new file mode 100644 index 0000000..86418ab --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_abstract_control.php.inc @@ -0,0 +1,12 @@ +template->yes = 'true'; + } else { + $this->template->no = 'false'; + } + + $this->template->render(__DIR__ . '/someFile.latte'); + } +} diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_double_set_file.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_double_set_file.php.inc new file mode 100644 index 0000000..7adc6bb --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_double_set_file.php.inc @@ -0,0 +1,19 @@ +template->value = 'key'; + + if (mt_rand(0, 100)) { + $this->template->setFile('one.latte'); + } else { + $this->template->setFile('two.latte'); + } + } +} diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_just_double_render.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_just_double_render.php.inc new file mode 100644 index 0000000..1457bea --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_just_double_render.php.inc @@ -0,0 +1,17 @@ +template->render(__DIR__ . '/first.latte'); + } else { + $this->template->render(__DIR__ . '/second.latte'); + } + } +} diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_missing_render.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_missing_render.php.inc new file mode 100644 index 0000000..2a2702c --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_missing_render.php.inc @@ -0,0 +1,14 @@ +template->value = 'key'; + $this->template->setFile('one.latte'); + } +} diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_presenter.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_presenter.php.inc new file mode 100644 index 0000000..5a2f8ac --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_presenter.php.inc @@ -0,0 +1,14 @@ +template->param = 'some value'; + $this->template->render(__DIR__ . '/poll.latte'); + } +} diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_render_with_missing_template.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_render_with_missing_template.php.inc new file mode 100644 index 0000000..1c31561 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/skip_render_with_missing_template.php.inc @@ -0,0 +1,14 @@ +template->key = 'value'; + $this->template->render(); + } +} diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/some_control.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/some_control.php.inc new file mode 100644 index 0000000..bcd0149 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/some_control.php.inc @@ -0,0 +1,32 @@ +template->param = 'some value'; + $this->template->render(__DIR__ . '/poll.latte'); + } +} + +?> +----- +template->render(__DIR__ . '/poll.latte', ['param' => 'some value']); + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/some_control_with_custom_render.php.inc b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/some_control_with_custom_render.php.inc new file mode 100644 index 0000000..4672ecd --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/Fixture/some_control_with_custom_render.php.inc @@ -0,0 +1,32 @@ +template->param = 'some value'; + $this->template->render(__DIR__ . '/poll.latte'); + } +} + +?> +----- +template->render(__DIR__ . '/poll.latte', ['param' => 'some value']); + } +} + +?> diff --git a/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/TemplateMagicAssignToExplicitVariableArrayRectorTest.php b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/TemplateMagicAssignToExplicitVariableArrayRectorTest.php new file mode 100644 index 0000000..9e4ceb1 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TemplateMagicAssignToExplicitVariableArrayRector/TemplateMagicAssignToExplicitVariableArrayRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return TemplateMagicAssignToExplicitVariableArrayRector::class; + } +} diff --git a/tests/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector/Fixture/fixture.php.inc b/tests/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..b21c1d6 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector/Fixture/fixture.php.inc @@ -0,0 +1,32 @@ + +----- + diff --git a/tests/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector/TranslateClassMethodToVariadicsRectorTest.php b/tests/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector/TranslateClassMethodToVariadicsRectorTest.php new file mode 100644 index 0000000..0e6ba95 --- /dev/null +++ b/tests/Nette/Rector/ClassMethod/TranslateClassMethodToVariadicsRector/TranslateClassMethodToVariadicsRectorTest.php @@ -0,0 +1,42 @@ +smartFileSystem->remove($localFilePath); + } + + require_once __DIR__ . '/../../../../../stubs/Nette/Localization/ITranslation.php'; + + // to make test work with fixture + $this->doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return TranslateClassMethodToVariadicsRector::class; + } +} diff --git a/tests/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector/FilePutContentsToFileSystemWriteRectorTest.php b/tests/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector/FilePutContentsToFileSystemWriteRectorTest.php new file mode 100644 index 0000000..b099b6c --- /dev/null +++ b/tests/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector/FilePutContentsToFileSystemWriteRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return FilePutContentsToFileSystemWriteRector::class; + } +} diff --git a/tests/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector/Fixture/fixture.php.inc b/tests/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..edd35e0 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/FilePutContentsToFileSystemWriteRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/Fixture/json_decode.php.inc b/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/Fixture/json_decode.php.inc new file mode 100644 index 0000000..f716385 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/Fixture/json_decode.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/Fixture/json_encode.php.inc b/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/Fixture/json_encode.php.inc new file mode 100644 index 0000000..e0a856c --- /dev/null +++ b/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/Fixture/json_encode.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRectorTest.php b/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRectorTest.php new file mode 100644 index 0000000..4fea3c9 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector/JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return JsonDecodeEncodeToNetteUtilsJsonDecodeEncodeRector::class; + } +} diff --git a/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace.php.inc b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace.php.inc new file mode 100644 index 0000000..cd7f390 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace_callback.php.inc b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace_callback.php.inc new file mode 100644 index 0000000..464426d --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace_callback.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace_with_string.php.inc b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace_with_string.php.inc new file mode 100644 index 0000000..6e7818f --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_replace_with_string.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_split.php.inc b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_split.php.inc new file mode 100644 index 0000000..a758a8f --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_split.php.inc @@ -0,0 +1,39 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_split_with_subpattern.php.inc b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_split_with_subpattern.php.inc new file mode 100644 index 0000000..8776c0e --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/Fixture/preg_split_with_subpattern.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/PregFunctionToNetteUtilsStringsRectorTest.php b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/PregFunctionToNetteUtilsStringsRectorTest.php new file mode 100644 index 0000000..3b85c29 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregFunctionToNetteUtilsStringsRector/PregFunctionToNetteUtilsStringsRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return PregFunctionToNetteUtilsStringsRector::class; + } +} diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_2_arguments.php.inc b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_2_arguments.php.inc new file mode 100644 index 0000000..d9fd016 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_2_arguments.php.inc @@ -0,0 +1,39 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_3_arguments.php.inc b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_3_arguments.php.inc new file mode 100644 index 0000000..eebfba7 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_3_arguments.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_2_arguments.php.inc b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_2_arguments.php.inc new file mode 100644 index 0000000..b19b721 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_2_arguments.php.inc @@ -0,0 +1,39 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_3_arguments.php.inc b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_3_arguments.php.inc new file mode 100644 index 0000000..3d6b22e --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_3_arguments.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_keep_format.php.inc b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_keep_format.php.inc new file mode 100644 index 0000000..66db3a4 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_all_keep_format.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_identical_one.php.inc b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_identical_one.php.inc new file mode 100644 index 0000000..b6d83f4 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_identical_one.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_identical_one_with_matches.php.inc b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_identical_one_with_matches.php.inc new file mode 100644 index 0000000..77f01aa --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/Fixture/preg_match_identical_one_with_matches.php.inc @@ -0,0 +1,33 @@ +matches = $matches; + + return $haveResults; + } +} + +?> +----- +matches = $matches; + + return $haveResults; + } +} + +?> diff --git a/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/PregMatchFunctionToNetteUtilsStringsRectorTest.php b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/PregMatchFunctionToNetteUtilsStringsRectorTest.php new file mode 100644 index 0000000..a32abc8 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/PregMatchFunctionToNetteUtilsStringsRector/PregMatchFunctionToNetteUtilsStringsRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return PregMatchFunctionToNetteUtilsStringsRector::class; + } +} diff --git a/tests/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector/Fixture/substr.php.inc b/tests/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector/Fixture/substr.php.inc new file mode 100644 index 0000000..f47edd2 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector/Fixture/substr.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/tests/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector/SubstrStrlenFunctionToNetteUtilsStringsRectorTest.php b/tests/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector/SubstrStrlenFunctionToNetteUtilsStringsRectorTest.php new file mode 100644 index 0000000..1e5ab78 --- /dev/null +++ b/tests/Nette/Rector/FuncCall/SubstrStrlenFunctionToNetteUtilsStringsRector/SubstrStrlenFunctionToNetteUtilsStringsRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return SubstrStrlenFunctionToNetteUtilsStringsRector::class; + } +} diff --git a/tests/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector/EndsWithFunctionToNetteUtilsStringsRectorTest.php b/tests/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector/EndsWithFunctionToNetteUtilsStringsRectorTest.php new file mode 100644 index 0000000..e20eac2 --- /dev/null +++ b/tests/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector/EndsWithFunctionToNetteUtilsStringsRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return EndsWithFunctionToNetteUtilsStringsRector::class; + } +} diff --git a/tests/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector/Fixture/fixture.php.inc b/tests/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..69632cb --- /dev/null +++ b/tests/Nette/Rector/Identical/EndsWithFunctionToNetteUtilsStringsRector/Fixture/fixture.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/tests/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector/Fixture/fixture.php.inc b/tests/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..a5f9275 --- /dev/null +++ b/tests/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector/Fixture/fixture.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/tests/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector/StartsWithFunctionToNetteUtilsStringsRectorTest.php b/tests/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector/StartsWithFunctionToNetteUtilsStringsRectorTest.php new file mode 100644 index 0000000..3d96885 --- /dev/null +++ b/tests/Nette/Rector/Identical/StartsWithFunctionToNetteUtilsStringsRector/StartsWithFunctionToNetteUtilsStringsRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return StartsWithFunctionToNetteUtilsStringsRector::class; + } +} diff --git a/tests/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector/Fixture/fixture.php.inc b/tests/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..c9409b6 --- /dev/null +++ b/tests/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector/Fixture/fixture.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/tests/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector/ReplaceTimeNumberWithDateTimeConstantRectorTest.php b/tests/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector/ReplaceTimeNumberWithDateTimeConstantRectorTest.php new file mode 100644 index 0000000..f391365 --- /dev/null +++ b/tests/Nette/Rector/LNumber/ReplaceTimeNumberWithDateTimeConstantRector/ReplaceTimeNumberWithDateTimeConstantRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ReplaceTimeNumberWithDateTimeConstantRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/AddNextrasDatePickerToDateControlRectorTest.php b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/AddNextrasDatePickerToDateControlRectorTest.php new file mode 100644 index 0000000..59042fe --- /dev/null +++ b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/AddNextrasDatePickerToDateControlRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return AddNextrasDatePickerToDateControlRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/add_rule.php.inc b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/add_rule.php.inc new file mode 100644 index 0000000..325c561 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/add_rule.php.inc @@ -0,0 +1,35 @@ +addDatePicker('key', 'Label') + ->addRule('...'); + } +} + +?> +----- +addRule('...'); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/add_rule_chaing_calls.php.inc b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/add_rule_chaing_calls.php.inc new file mode 100644 index 0000000..6290da8 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/add_rule_chaing_calls.php.inc @@ -0,0 +1,37 @@ +addDatePicker('key', 'Label') + ->addRule('...') + ->addContitionOn('...'); + } +} + +?> +----- +addRule('...') + ->addContitionOn('...'); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/assigned_value.php.inc b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/assigned_value.php.inc new file mode 100644 index 0000000..fcaa4c4 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/assigned_value.php.inc @@ -0,0 +1,33 @@ +addDatePicker('key', 'Label'); + } +} + +?> +----- + diff --git a/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..41311eb --- /dev/null +++ b/tests/Nette/Rector/MethodCall/AddNextrasDatePickerToDateControlRector/Fixture/fixture.php.inc @@ -0,0 +1,33 @@ +addDatePicker('key', 'Label'); + } +} + +?> +----- + diff --git a/tests/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector/BuilderExpandToHelperExpandRectorTest.php b/tests/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector/BuilderExpandToHelperExpandRectorTest.php new file mode 100644 index 0000000..4777472 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector/BuilderExpandToHelperExpandRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return BuilderExpandToHelperExpandRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..1d85b14 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/BuilderExpandToHelperExpandRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ +getContainerBuilder()->expand('%value'); + } +} + +?> +----- +getContainerBuilder()->parameters); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/ContextGetByTypeToConstructorInjectionRectorTest.php b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/ContextGetByTypeToConstructorInjectionRectorTest.php new file mode 100644 index 0000000..6e367e0 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/ContextGetByTypeToConstructorInjectionRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ContextGetByTypeToConstructorInjectionRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/existing_constructor_param.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/existing_constructor_param.php.inc new file mode 100644 index 0000000..1890ec5 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/existing_constructor_param.php.inc @@ -0,0 +1,61 @@ +someInterfaceToInject = $someInterfaceToInject; + } + + public function run() + { + $someTypeToInject = $this->context->getByType(ISomeInterfaceToInject::class); + } +} + +?> +----- +someInterfaceToInject = $someInterfaceToInject; + } + + public function run() + { + $someTypeToInject = $this->someInterfaceToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..f0a7f80 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/fixture.php.inc @@ -0,0 +1,44 @@ +context->getByType(SomeTypeToInject::class); + } +} + +?> +----- +someTypeToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/interface_name.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/interface_name.php.inc new file mode 100644 index 0000000..f46b311 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/interface_name.php.inc @@ -0,0 +1,44 @@ +context->getByType(ISomeInterfaceToInject::class); + } +} + +?> +----- +someInterfaceToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_parent_construct_prefer_local_constructor_presenter.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_parent_construct_prefer_local_constructor_presenter.php.inc new file mode 100644 index 0000000..7e020e1 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_parent_construct_prefer_local_constructor_presenter.php.inc @@ -0,0 +1,54 @@ +context->getByType(ISomeInterfaceToInject::class); + } +} + +?> +----- +someInterfaceToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_parent_construct_presenter.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_parent_construct_presenter.php.inc new file mode 100644 index 0000000..966274e --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_parent_construct_presenter.php.inc @@ -0,0 +1,47 @@ +context->getByType(ISomeInterfaceToInject::class); + } +} + +?> +----- +someInterfaceToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_presenter.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_presenter.php.inc new file mode 100644 index 0000000..835c4ec --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/respect_inject_presenter.php.inc @@ -0,0 +1,57 @@ +context->getByType(ISomeInterfaceToInject::class); + } +} + +?> +----- +someInterfaceToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/reuse_parent_property_with_same_type.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/reuse_parent_property_with_same_type.php.inc new file mode 100644 index 0000000..01a2bd0 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/reuse_parent_property_with_same_type.php.inc @@ -0,0 +1,43 @@ +context->getByType(SomeTypeToInject::class); + } +} + +?> +----- +someTypeToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/skip_test.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/skip_test.php.inc new file mode 100644 index 0000000..719fd5e --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Fixture/skip_test.php.inc @@ -0,0 +1,19 @@ +context->getByType(SomeTypeToInject::class); + } +} diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/FixturePhp74/some_service.php.inc b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/FixturePhp74/some_service.php.inc new file mode 100644 index 0000000..82010a2 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/FixturePhp74/some_service.php.inc @@ -0,0 +1,38 @@ +context->getByType(ISomeInterfaceToInject::class); + } +} + +?> +----- +someInterfaceToInject; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Php74Test.php b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Php74Test.php new file mode 100644 index 0000000..abd7382 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Php74Test.php @@ -0,0 +1,35 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixturePhp74'); + } + + protected function getRectorClass(): string + { + return ContextGetByTypeToConstructorInjectionRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/ConstructorInjectionParentPresenter.php b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/ConstructorInjectionParentPresenter.php new file mode 100644 index 0000000..9bd0dda --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/ConstructorInjectionParentPresenter.php @@ -0,0 +1,26 @@ +someTypeToInject = $someTypeToInject; + } + + function run(Request $request): IResponse + { + } +} diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/ISomeInterfaceToInject.php b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/ISomeInterfaceToInject.php new file mode 100644 index 0000000..e1a5149 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/ISomeInterfaceToInject.php @@ -0,0 +1,10 @@ +someTypeToInject = $someTypeToInject; + } + + function run(Request $request): IResponse + { + } +} diff --git a/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/SomeTypeToInject.php b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/SomeTypeToInject.php new file mode 100644 index 0000000..d0ff95f --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ContextGetByTypeToConstructorInjectionRector/Source/SomeTypeToInject.php @@ -0,0 +1,10 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..dec166c --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/fixture.php.inc @@ -0,0 +1,33 @@ +addUpload('a', 'a', true); + } +} + +?> +----- +addMultiUpload('a', 'a'); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_no_third_argument.php.inc b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_no_third_argument.php.inc new file mode 100644 index 0000000..3da28b2 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_no_third_argument.php.inc @@ -0,0 +1,16 @@ +addUpload('a', 'a'); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_not_nette_forms_form.php.inc b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_not_nette_forms_form.php.inc new file mode 100644 index 0000000..b1b1521 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_not_nette_forms_form.php.inc @@ -0,0 +1,16 @@ +format('Y-m-d'); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_not_upload_method_call.php.inc b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_not_upload_method_call.php.inc new file mode 100644 index 0000000..ab87735 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/ConvertAddUploadWithThirdArgumentTrueToAddMultiUploadRector/Fixture/skip_not_upload_method_call.php.inc @@ -0,0 +1,16 @@ +setHtmlAttribute('a', 'a'); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..db65d1c --- /dev/null +++ b/tests/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector/Fixture/fixture.php.inc @@ -0,0 +1,33 @@ +setClass('first'); + } +} + +?> +----- +appendAttribute('class', 'first'); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector/MagicHtmlCallToAppendAttributeRectorTest.php b/tests/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector/MagicHtmlCallToAppendAttributeRectorTest.php new file mode 100644 index 0000000..4152cfd --- /dev/null +++ b/tests/Nette/Rector/MethodCall/MagicHtmlCallToAppendAttributeRector/MagicHtmlCallToAppendAttributeRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return MagicHtmlCallToAppendAttributeRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..c1b7733 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/Fixture/fixture.php.inc @@ -0,0 +1,39 @@ + 'value' + ]; + + public function loadConfiguration() + { + $config = $this->getConfig($this->defaults); + } +} + +?> +----- + 'value' + ]; + + public function loadConfiguration() + { + $config = array_merge($this->defaults, $this->getConfig()); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/Fixture/skip_empty_get_config.php.inc b/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/Fixture/skip_empty_get_config.php.inc new file mode 100644 index 0000000..13456db --- /dev/null +++ b/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/Fixture/skip_empty_get_config.php.inc @@ -0,0 +1,17 @@ + 'value' + ]; + + public function loadConfiguration() + { + $config = $this->getConfig(); + } +} diff --git a/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/MergeDefaultsInGetConfigCompilerExtensionRectorTest.php b/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/MergeDefaultsInGetConfigCompilerExtensionRectorTest.php new file mode 100644 index 0000000..98358e8 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/MergeDefaultsInGetConfigCompilerExtensionRector/MergeDefaultsInGetConfigCompilerExtensionRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return MergeDefaultsInGetConfigCompilerExtensionRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..d1a8895 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ +getCookie('name', 'default'); + } +} + +?> +----- +getCookie('name') ?? 'default'; + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/Fixture/skip_non_2nd_arg.php.inc b/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/Fixture/skip_non_2nd_arg.php.inc new file mode 100644 index 0000000..b89f898 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/Fixture/skip_non_2nd_arg.php.inc @@ -0,0 +1,13 @@ +getCookie('name'); + } +} diff --git a/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/RequestGetCookieDefaultArgumentToCoalesceRectorTest.php b/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/RequestGetCookieDefaultArgumentToCoalesceRectorTest.php new file mode 100644 index 0000000..210184e --- /dev/null +++ b/tests/Nette/Rector/MethodCall/RequestGetCookieDefaultArgumentToCoalesceRector/RequestGetCookieDefaultArgumentToCoalesceRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return RequestGetCookieDefaultArgumentToCoalesceRector::class; + } +} diff --git a/tests/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector/Fixture/fixture.php.inc b/tests/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..e0aefdf --- /dev/null +++ b/tests/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector/Fixture/fixture.php.inc @@ -0,0 +1,33 @@ +addDefinition('...') + ->setClass('SomeClass', [1, 2]); + } +} + +?> +----- +addDefinition('...') + ->setFactory('SomeClass', [1, 2]); + } +} + +?> diff --git a/tests/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector/SetClassWithArgumentToSetFactoryRectorTest.php b/tests/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector/SetClassWithArgumentToSetFactoryRectorTest.php new file mode 100644 index 0000000..c0bf316 --- /dev/null +++ b/tests/Nette/Rector/MethodCall/SetClassWithArgumentToSetFactoryRector/SetClassWithArgumentToSetFactoryRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return SetClassWithArgumentToSetFactoryRector::class; + } +} diff --git a/tests/Nette/Rector/NotIdentical/StrposToStringsContainsRector/Fixture/fixture.php.inc b/tests/Nette/Rector/NotIdentical/StrposToStringsContainsRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..dfd92c6 --- /dev/null +++ b/tests/Nette/Rector/NotIdentical/StrposToStringsContainsRector/Fixture/fixture.php.inc @@ -0,0 +1,41 @@ + +----- + diff --git a/tests/Nette/Rector/NotIdentical/StrposToStringsContainsRector/Fixture/keep.php.inc b/tests/Nette/Rector/NotIdentical/StrposToStringsContainsRector/Fixture/keep.php.inc new file mode 100644 index 0000000..9e3991e --- /dev/null +++ b/tests/Nette/Rector/NotIdentical/StrposToStringsContainsRector/Fixture/keep.php.inc @@ -0,0 +1,12 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return StrposToStringsContainsRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/AnnotateMagicalControlArrayAccessRectorTest.php b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/AnnotateMagicalControlArrayAccessRectorTest.php new file mode 100644 index 0000000..2f24976 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/AnnotateMagicalControlArrayAccessRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return AnnotateMagicalControlArrayAccessRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/above_array_dim_fetch_set.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/above_array_dim_fetch_set.php.inc new file mode 100644 index 0000000..8fd6992 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/above_array_dim_fetch_set.php.inc @@ -0,0 +1,43 @@ +callThis()) { + } + } +} + +?> +----- +callThis()) { + } + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/fixture.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..556412c --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/fixture.php.inc @@ -0,0 +1,47 @@ +isSubmitted()) { + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> +----- +isSubmitted()) { + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/in_switch.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/in_switch.php.inc new file mode 100644 index 0000000..d8feac5 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/in_switch.php.inc @@ -0,0 +1,67 @@ +getValues(); + case 'another_form': + return $this['another_form']->getValues(); + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } + + protected function createComponentAnotherForm() + { + return new Form(); + } +} + +?> +----- +getValues(); + case 'another_form': + return $anotherForm->getValues(); + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } + + protected function createComponentAnotherForm() + { + return new Form(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/nested_in_if.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/nested_in_if.php.inc new file mode 100644 index 0000000..f6c837a --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/nested_in_if.php.inc @@ -0,0 +1,57 @@ +call(); + } + + if ($this['someForm']) { + return null; + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> +----- +call(); + } + + if ($someForm) { + return null; + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/nested_isset_unset.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/nested_isset_unset.php.inc new file mode 100644 index 0000000..a7ba3b0 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/nested_isset_unset.php.inc @@ -0,0 +1,53 @@ + +----- + diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/on_parent_create_component.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/on_parent_create_component.php.inc new file mode 100644 index 0000000..759847f --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/on_parent_create_component.php.inc @@ -0,0 +1,51 @@ +onSuccces[] = 'yes'; + } +} + +abstract class SomeParentPresenter extends Presenter +{ + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> +----- +onSuccces[] = 'yes'; + } +} + +abstract class SomeParentPresenter extends Presenter +{ + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/race_condition_single_nested_isset_unset.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/race_condition_single_nested_isset_unset.php.inc new file mode 100644 index 0000000..fbf8c51 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/race_condition_single_nested_isset_unset.php.inc @@ -0,0 +1,57 @@ + +----- + diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/rename_nested_too.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/rename_nested_too.php.inc new file mode 100644 index 0000000..016f707 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/rename_nested_too.php.inc @@ -0,0 +1,49 @@ +isSubmitted()) { + return $this['some_form']->getValues(); + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> +----- +isSubmitted()) { + return $someForm->getValues(); + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_already.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_already.php.inc new file mode 100644 index 0000000..c975dd0 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_already.php.inc @@ -0,0 +1,22 @@ +isSubmitted()) { + } + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_nested_control.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_nested_control.php.inc new file mode 100644 index 0000000..4239641 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_nested_control.php.inc @@ -0,0 +1,14 @@ +isSubmitted()) { + } + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_single_nested_isset_unset.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_single_nested_isset_unset.php.inc new file mode 100644 index 0000000..299890e --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/AnnotateMagicalControlArrayAccessRector/Fixture/skip_single_nested_isset_unset.php.inc @@ -0,0 +1,21 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ChangeFormArrayAccessToAnnotatedControlVariableRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/add_rule_call.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/add_rule_call.php.inc new file mode 100644 index 0000000..34868a0 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/add_rule_call.php.inc @@ -0,0 +1,57 @@ +addText('phone_number', 'Phone number') + ->addRule(function (Form $control) { + return $this->parent instanceof SomeControl + ? $this->parent->isPhoneNumberValid($control->getValue()) + : true; + }, 'Invalid phone number.') + ->setRequired(); + + $this->addText('code', 'verify'); + + $this['code']->addConditionOn($this['phone_number'], Form::EQUAL, 2) + ->setRequired(); + } +} + +?> +----- +addText('phone_number', 'Phone number') + ->addRule(function (Form $control) { + return $this->parent instanceof SomeControl + ? $this->parent->isPhoneNumberValid($control->getValue()) + : true; + }, 'Invalid phone number.') + ->setRequired(); + + $this->addText('code', 'verify'); + /** @var \Nette\Forms\Controls\TextInput $codeControl */ + $codeControl = $this['code']; + /** @var \Nette\Forms\Controls\TextInput $phoneNumberControl */ + $phoneNumberControl = $this['phone_number']; + + $codeControl->addConditionOn($phoneNumberControl, Form::EQUAL, 2) + ->setRequired(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/fixture.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..054c0e0 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/fixture.php.inc @@ -0,0 +1,37 @@ +addText('email', 'Email'); + $form['email']->value = 'hey@hi.hello'; + } +} + +?> +----- +addText('email', 'Email'); + /** @var \Nette\Forms\Controls\TextInput $emailControl */ + $emailControl = $form['email']; + $emailControl->value = 'hey@hi.hello'; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_another_call.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_another_call.php.inc new file mode 100644 index 0000000..8214ddc --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_another_call.php.inc @@ -0,0 +1,51 @@ +makeForm(); + $form['email_else']->value = 'hey@hi.hello'; + } + + public function makeForm(): Form + { + $form = new Form(); + $form->addText('email_else', 'Email'); + + return $form; + } +} + +?> +----- +makeForm(); + /** @var \Nette\Forms\Controls\TextInput $emailElseControl */ + $emailElseControl = $form['email_else']; + $emailElseControl->value = 'hey@hi.hello'; + } + + public function makeForm(): Form + { + $form = new Form(); + $form->addText('email_else', 'Email'); + + return $form; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_external_class.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_external_class.php.inc new file mode 100644 index 0000000..5609aa3 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_external_class.php.inc @@ -0,0 +1,55 @@ +someFormFactory = $someFormFactory; + } + + public function run() + { + $form = $this->someFormFactory->createForm(); + $form['items']->value = 'hey@hi.hello'; + } +} + +?> +----- +someFormFactory = $someFormFactory; + } + + public function run() + { + $form = $this->someFormFactory->createForm(); + /** @var \Nette\Forms\Controls\SelectBox $itemsControl */ + $itemsControl = $form['items']; + $itemsControl->value = 'hey@hi.hello'; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_new_instance_elsewhere.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_new_instance_elsewhere.php.inc new file mode 100644 index 0000000..7ea1dcb --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_build_in_new_instance_elsewhere.php.inc @@ -0,0 +1,51 @@ +value = 'hey@hi.hello'; + } +} + +class LocalForm extends Form +{ + public function __construct() + { + $this->addText('unique_name'); + } +} + +?> +----- +value = 'hey@hi.hello'; + } +} + +class LocalForm extends Form +{ + public function __construct() + { + $this->addText('unique_name'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_via_get_component_in_method.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_via_get_component_in_method.php.inc new file mode 100644 index 0000000..f1aa8dd --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_via_get_component_in_method.php.inc @@ -0,0 +1,59 @@ +getComponent('some'); + $form['title_inner']->value = 'wohoooo'; + } + + public function createComponentSome(): ExternalFormWithTitleInMethod + { + $form = new ExternalFormWithTitleInMethod(); + $form->addText('title_inner'); + return $form; + } +} + +final class ExternalFormWithTitleInMethod extends Form +{ +} + +?> +----- +getComponent('some'); + /** @var \Nette\Forms\Controls\TextInput $titleInnerControl */ + $titleInnerControl = $form['title_inner']; + $titleInnerControl->value = 'wohoooo'; + } + + public function createComponentSome(): ExternalFormWithTitleInMethod + { + $form = new ExternalFormWithTitleInMethod(); + $form->addText('title_inner'); + return $form; + } +} + +final class ExternalFormWithTitleInMethod extends Form +{ +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_with_add_rule.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_with_add_rule.php.inc new file mode 100644 index 0000000..4bbc383 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/form_with_add_rule.php.inc @@ -0,0 +1,53 @@ +makeForm(); + $form['text']->value = 'hey@hi.hello'; + } + + public function makeForm(): Form + { + $form = new Form(); + $form->addTextArea('text', 'Text') + ->addRule([$this, 'check']); + + return $form; + } +} + +?> +----- +makeForm(); + /** @var \Nette\Forms\Controls\TextArea $textControl */ + $textControl = $form['text']; + $textControl->value = 'hey@hi.hello'; + } + + public function makeForm(): Form + { + $form = new Form(); + $form->addTextArea('text', 'Text') + ->addRule([$this, 'check']); + + return $form; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/in_on_success_call.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/in_on_success_call.php.inc new file mode 100644 index 0000000..c1174a9 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/in_on_success_call.php.inc @@ -0,0 +1,57 @@ +makeForm(); + + $onSuccess = function (Form $form) { + $form['email_else']->value = 'hey@hi.hello'; + }; + } + + public function makeForm(): Form + { + $form = new Form(); + $form->addText('email_else', 'Email'); + + return $form; + } +} + +?> +----- +makeForm(); + + $onSuccess = function (Form $form) { + /** @var \Nette\Forms\Controls\TextInput $emailElseControl */ + $emailElseControl = $form['email_else']; + $emailElseControl->value = 'hey@hi.hello'; + }; + } + + public function makeForm(): Form + { + $form = new Form(); + $form->addText('email_else', 'Email'); + + return $form; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_multiplier.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_multiplier.php.inc new file mode 100644 index 0000000..b77f9b5 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/nested_multiplier.php.inc @@ -0,0 +1,57 @@ +create(); + $form['name']->getControlPrototype() + ->addAttributes(['class' => 'first']); + }); + } + + public function create(): Form + { + $form = new Form(); + $form->addText('name'); + return $form; + } +} + +?> +----- +create(); + /** @var \Nette\Forms\Controls\TextInput $nameControl */ + $nameControl = $form['name']; + $nameControl->getControlPrototype() + ->addAttributes(['class' => 'first']); + }); + } + + public function create(): Form + { + $form = new Form(); + $form->addText('name'); + return $form; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/param_type.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/param_type.php.inc new file mode 100644 index 0000000..6407ce2 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/param_type.php.inc @@ -0,0 +1,49 @@ +getValues(); + } +} + +final class SomeForm extends Form +{ + public function __construct() + { + $this->addRadioList('items'); + } +} + +?> +----- +getValues(); + } +} + +final class SomeForm extends Form +{ + public function __construct() + { + $this->addRadioList('items'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/parent_var_construct.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/parent_var_construct.php.inc new file mode 100644 index 0000000..0dfaf38 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/parent_var_construct.php.inc @@ -0,0 +1,59 @@ +setItems([1, 2, 3]); + } +} + +final class ContestForm extends Form +{ + public function __construct(?IContainer $parent = null, $name = null) + { + parent::__construct($parent, $name); + + $this->addSelect('not_found'); + } +} + +?> +----- +setItems([1, 2, 3]); + } +} + +final class ContestForm extends Form +{ + public function __construct(?IContainer $parent = null, $name = null) + { + parent::__construct($parent, $name); + + $this->addSelect('not_found'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/prevent_duplicates.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/prevent_duplicates.php.inc new file mode 100644 index 0000000..f0b3902 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/prevent_duplicates.php.inc @@ -0,0 +1,47 @@ +addTextArea('text', 'Text'); + $this->addUpload('file', 'Attachment') + ->addRule(Form::IMAGE) + ->setRequired(false); + + $this['text']->addConditionOn($this['file'], Form::BLANK)->setRequired(); + $this['file']->getControlPrototype()->accept = 'image/*'; + } +} + +?> +----- +addTextArea('text', 'Text'); + $this->addUpload('file', 'Attachment') + ->addRule(Form::IMAGE) + ->setRequired(false); + /** @var \Nette\Forms\Controls\TextArea $textControl */ + $textControl = $this['text']; + /** @var \Nette\Forms\Controls\UploadControl $fileControl */ + $fileControl = $this['file']; + + $textControl->addConditionOn($fileControl, Form::BLANK)->setRequired(); + $fileControl->getControlPrototype()->accept = 'image/*'; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/referencing_self_construct.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/referencing_self_construct.php.inc new file mode 100644 index 0000000..ae88bb5 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/referencing_self_construct.php.inc @@ -0,0 +1,47 @@ +addText('name'); + } + + public function anotherMethod() + { + if (! empty($this['name']->value)) { + return; + } + } +} + +?> +----- +addText('name'); + } + + public function anotherMethod() + { + /** @var \Nette\Forms\Controls\TextInput $nameControl */ + $nameControl = $this['name']; + if (! empty($nameControl->value)) { + return; + } + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/select_box.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/select_box.php.inc new file mode 100644 index 0000000..6cbe6a6 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/select_box.php.inc @@ -0,0 +1,37 @@ +addSelect('items', 'Email'); + $form['items']->value = 'hey@hi.hello'; + } +} + +?> +----- +addSelect('items', 'Email'); + /** @var \Nette\Forms\Controls\SelectBox $itemsControl */ + $itemsControl = $form['items']; + $itemsControl->value = 'hey@hi.hello'; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/self_construct.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/self_construct.php.inc new file mode 100644 index 0000000..a464864 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/self_construct.php.inc @@ -0,0 +1,37 @@ +addText('name'); + $this['name']->value = 5; + } +} + +?> +----- +addText('name'); + /** @var \Nette\Forms\Controls\TextInput $nameControl */ + $nameControl = $this['name']; + $nameControl->value = 5; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_assign.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_assign.php.inc new file mode 100644 index 0000000..fb6f3d3 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_assign.php.inc @@ -0,0 +1,15 @@ +setRequired(true); + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_assigned.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_assigned.php.inc new file mode 100644 index 0000000..f2f2ca7 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_assigned.php.inc @@ -0,0 +1,14 @@ +values(); + } + + public function createComponentSomeForm(): Form + { + return new Form(); + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_in_isset_unset.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_in_isset_unset.php.inc new file mode 100644 index 0000000..f1c270f --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_in_isset_unset.php.inc @@ -0,0 +1,17 @@ +addText('email', 'Email'); + if (isset($form['email'])) { + unset($form['email']); + } + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_on_right_assign.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_on_right_assign.php.inc new file mode 100644 index 0000000..2bd3436 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_on_right_assign.php.inc @@ -0,0 +1,17 @@ +addText('email', 'Email'); + + /** @var \Nette\Forms\Controls\BaseControl $emailControl */ + $emailControl = $form['email']; + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_unset.php.inc b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_unset.php.inc new file mode 100644 index 0000000..006bd1e --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Fixture/skip_unset.php.inc @@ -0,0 +1,15 @@ +addText('title'); + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/Multiplier.php b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/Multiplier.php new file mode 100644 index 0000000..ac1797b --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/Multiplier.php @@ -0,0 +1,12 @@ +addSelect('items'); + + return $form; + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoForm.php b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoForm.php new file mode 100644 index 0000000..eecfed3 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoForm.php @@ -0,0 +1,15 @@ +addCheckboxList('video'); + } +} diff --git a/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoFormFactory.php b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoFormFactory.php new file mode 100644 index 0000000..ee28fc5 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ArrayDimFetch/ChangeFormArrayAccessToAnnotatedControlVariableRector/Source/VideoFormFactory.php @@ -0,0 +1,10 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ArrayAccessGetControlToGetComponentMethodCallRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/create_component.php.inc b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/create_component.php.inc new file mode 100644 index 0000000..a847239 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/create_component.php.inc @@ -0,0 +1,41 @@ + +----- +getComponent('someForm'); + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/fixture.php.inc b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..72eca2c --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/fixture.php.inc @@ -0,0 +1,31 @@ + +----- +getComponent('whatever'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/skip_assign_to.php.inc b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/skip_assign_to.php.inc new file mode 100644 index 0000000..817274d --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessGetControlToGetComponentMethodCallRector/Fixture/skip_assign_to.php.inc @@ -0,0 +1,19 @@ +doThis(); + } + + protected function createComponentSomeForm() + { + return new Form(); + } +} diff --git a/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/ArrayAccessSetControlToAddComponentMethodCallRectorTest.php b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/ArrayAccessSetControlToAddComponentMethodCallRectorTest.php new file mode 100644 index 0000000..c654886 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/ArrayAccessSetControlToAddComponentMethodCallRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ArrayAccessSetControlToAddComponentMethodCallRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/Fixture/fixture.php.inc b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..571abfa --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/Fixture/fixture.php.inc @@ -0,0 +1,35 @@ + +----- +addComponent($someControl, 'whatever'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/Fixture/skip_form.php.inc b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/Fixture/skip_form.php.inc new file mode 100644 index 0000000..c3ed061 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/ArrayAccessSetControlToAddComponentMethodCallRector/Fixture/skip_form.php.inc @@ -0,0 +1,15 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/FixtureAutoImport'); + } + + protected function provideConfigFilePath(): string + { + return __DIR__ . '/config/auto_import.php'; + } +} diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/array_dim_access.php.inc b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/array_dim_access.php.inc new file mode 100644 index 0000000..47cd4b8 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/array_dim_access.php.inc @@ -0,0 +1,34 @@ + +----- + diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/fixture.php.inc b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..29e3a1a --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/fixture.php.inc @@ -0,0 +1,34 @@ +getComponent('another'); + } +} + +?> +----- +getComponent('another'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/in_presenter.php.inc b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/in_presenter.php.inc new file mode 100644 index 0000000..53ab067 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/in_presenter.php.inc @@ -0,0 +1,50 @@ +getComponent('another'); + } + + /** + * @return AnotherControl + */ + protected function createComponentAnother() + { + return new AnotherControl(); + } +} + +?> +----- +getComponent('another'); + } + + /** + * @return AnotherControl + */ + protected function createComponentAnother() + { + return new AnotherControl(); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/skip_already_doc.php.inc b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/skip_already_doc.php.inc new file mode 100644 index 0000000..86b07bf --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/skip_already_doc.php.inc @@ -0,0 +1,16 @@ +getComponent('another'); + } +} diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/skip_non_existing_method.php.inc b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/skip_non_existing_method.php.inc new file mode 100644 index 0000000..a5c9364 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Fixture/skip_non_existing_method.php.inc @@ -0,0 +1,15 @@ +getComponent('missing'); + } +} diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/FixtureAutoImport/auto_import_names.php.inc b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/FixtureAutoImport/auto_import_names.php.inc new file mode 100644 index 0000000..597bc6b --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/FixtureAutoImport/auto_import_names.php.inc @@ -0,0 +1,35 @@ +getComponent('another'); + } +} + +?> +----- +getComponent('another'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/MakeGetComponentAssignAnnotatedRectorTest.php b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/MakeGetComponentAssignAnnotatedRectorTest.php new file mode 100644 index 0000000..301ac16 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/MakeGetComponentAssignAnnotatedRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return MakeGetComponentAssignAnnotatedRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Source/AnotherControl.php b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Source/AnotherControl.php new file mode 100644 index 0000000..4094fe1 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Assign/MakeGetComponentAssignAnnotatedRector/Source/AnotherControl.php @@ -0,0 +1,11 @@ +parameters(); + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + $parameters->set(Option::IMPORT_DOC_BLOCKS, true); + + $services = $containerConfigurator->services(); + $services->set(MakeGetComponentAssignAnnotatedRector::class); +}; diff --git a/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_already_render.php.inc b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_already_render.php.inc new file mode 100644 index 0000000..be22372 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_already_render.php.inc @@ -0,0 +1,14 @@ +template->setFile(__DIR__ . '/someFile.latte'); + $this->template->render('another_path'); + } +} diff --git a/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_double_set_file.php.inc b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_double_set_file.php.inc new file mode 100644 index 0000000..2831e3d --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_double_set_file.php.inc @@ -0,0 +1,19 @@ +template->setFile(__DIR__ . '/someFile.latte'); + } else { + $this->template->setFile(__DIR__ . '/anotherFile.latte'); + } + + $this->template->render(); + } +} diff --git a/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_no_set_file.php.inc b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_no_set_file.php.inc new file mode 100644 index 0000000..c542b29 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/skip_no_set_file.php.inc @@ -0,0 +1,13 @@ +template->render(); + } +} diff --git a/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/some_class.php.inc b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/some_class.php.inc new file mode 100644 index 0000000..39ac630 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/Fixture/some_class.php.inc @@ -0,0 +1,32 @@ +template->setFile(__DIR__ . '/someFile.latte'); + $this->template->render(); + } +} + +?> +----- +template->render(__DIR__ . '/someFile.latte'); + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/MergeTemplateSetFileToTemplateRenderRectorTest.php b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/MergeTemplateSetFileToTemplateRenderRectorTest.php new file mode 100644 index 0000000..8bda861 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/ClassMethod/MergeTemplateSetFileToTemplateRenderRector/MergeTemplateSetFileToTemplateRenderRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return MergeTemplateSetFileToTemplateRenderRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Fixture/fixture.php.inc b/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..d914398 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Fixture/fixture.php.inc @@ -0,0 +1,49 @@ +otherDependency = $otherDependency; + } +} + +?> +----- +otherDependency = $otherDependency; + } +} + +?> diff --git a/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Fixture/skip_if_no_constructor.php.inc b/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Fixture/skip_if_no_constructor.php.inc new file mode 100644 index 0000000..48dc4f6 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Fixture/skip_if_no_constructor.php.inc @@ -0,0 +1,12 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return MoveInjectToExistingConstructorRector::class; + } +} diff --git a/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Source/ClassWithParentConstructor.php b/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Source/ClassWithParentConstructor.php new file mode 100644 index 0000000..763041b --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Class_/MoveInjectToExistingConstructorRector/Source/ClassWithParentConstructor.php @@ -0,0 +1,13 @@ + +----- + diff --git a/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/inside_if.php.inc b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/inside_if.php.inc new file mode 100644 index 0000000..f9d4a01 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/inside_if.php.inc @@ -0,0 +1,49 @@ + +----- + diff --git a/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/skip_different_length.php.inc b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/skip_different_length.php.inc new file mode 100644 index 0000000..41b3caa --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/skip_different_length.php.inc @@ -0,0 +1,10 @@ + diff --git a/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/skip_not_substr.php.inc b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/skip_not_substr.php.inc new file mode 100644 index 0000000..0450114 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/skip_not_substr.php.inc @@ -0,0 +1,10 @@ + diff --git a/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/with_var_value.php.inc b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/with_var_value.php.inc new file mode 100644 index 0000000..7304aa8 --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/Fixture/with_var_value.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/SubstrMinusToStringEndsWithRectorTest.php b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/SubstrMinusToStringEndsWithRectorTest.php new file mode 100644 index 0000000..99e189e --- /dev/null +++ b/tests/NetteCodeQuality/Rector/Identical/SubstrMinusToStringEndsWithRector/SubstrMinusToStringEndsWithRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return SubstrMinusToStringEndsWithRector::class; + } +} diff --git a/tests/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector/ChangeNetteEventNamesInGetSubscribedEventsRectorTest.php b/tests/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector/ChangeNetteEventNamesInGetSubscribedEventsRectorTest.php new file mode 100644 index 0000000..e3e3ccf --- /dev/null +++ b/tests/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector/ChangeNetteEventNamesInGetSubscribedEventsRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ChangeNetteEventNamesInGetSubscribedEventsRector::class; + } +} diff --git a/tests/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector/Fixture/application_on_shutdown.php.inc b/tests/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector/Fixture/application_on_shutdown.php.inc new file mode 100644 index 0000000..37b94b9 --- /dev/null +++ b/tests/NetteKdyby/Rector/ClassMethod/ChangeNetteEventNamesInGetSubscribedEventsRector/Fixture/application_on_shutdown.php.inc @@ -0,0 +1,48 @@ +getPresenter(); + } +} + +?> +----- + 'onShutdown', + ]; + } + + public function onShutdown(\Contributte\Events\Extra\Event\Application\ShutdownEvent $shutdownEvent): void + { + $application = $shutdownEvent->getApplication(); + $presenter = $application->getPresenter(); + } +} + +?> diff --git a/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/Fixture/fixture.php.inc b/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..355f63a --- /dev/null +++ b/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/Fixture/fixture.php.inc @@ -0,0 +1,68 @@ +onTomatoBuy($tomato, $userId); + } +} + +final class ActionLogEventSubscriber implements Subscriber +{ + public function getSubscribedEvents(): array + { + return [ + VegetableMarket::class . '::onTomatoBuy' => 'onTomatoBuy', + ]; + } + + public function onTomatoBuy(Tomato $anotherTomato, int $adminId): void + { + $anotherTomato->unwrap(); + } +} + +?> +----- +onTomatoBuy($tomato, $userId); + } +} + +final class ActionLogEventSubscriber implements Subscriber +{ + public function getSubscribedEvents(): array + { + return [ + \Rector\Tests\NetteKdyby\Rector\ClassMethod\ReplaceMagicPropertyWithEventClassRector\Fixture\Event\VegetableMarketTomatoBuyEvent::class => 'onTomatoBuy', + ]; + } + + public function onTomatoBuy(\Rector\Tests\NetteKdyby\Rector\ClassMethod\ReplaceMagicPropertyWithEventClassRector\Fixture\Event\VegetableMarketTomatoBuyEvent $vegetableMarketTomatoBuyEvent): void + { + $anotherTomato = $vegetableMarketTomatoBuyEvent->getTomato(); + $anotherTomato->unwrap(); + } +} + +?> diff --git a/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/ReplaceMagicPropertyWithEventClassRectorTest.php b/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/ReplaceMagicPropertyWithEventClassRectorTest.php new file mode 100644 index 0000000..467c97a --- /dev/null +++ b/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/ReplaceMagicPropertyWithEventClassRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return ReplaceMagicPropertyWithEventClassRector::class; + } +} diff --git a/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/Source/Tomato.php b/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/Source/Tomato.php new file mode 100644 index 0000000..3d89d78 --- /dev/null +++ b/tests/NetteKdyby/Rector/ClassMethod/ReplaceMagicPropertyWithEventClassRector/Source/Tomato.php @@ -0,0 +1,12 @@ +eventManager = $eventManager; + } + + public function run() + { + $key = '2000'; + $this->eventManager->dispatchEvent(static::class . '::onCopy', new EventArgsList([$this, $key])); + } +} + +?> +----- +eventManager = $eventManager; + } + + public function run() + { + $key = '2000'; + $this->eventManager->dispatch(new \Rector\Tests\NetteKdyby\Rector\MethodCall\ReplaceEventManagerWithEventSubscriberRector\Fixture\Event\SomeClassCopyEvent($this, $key)); + } +} + +?> diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/ReplaceEventManagerWithEventSubscriberRectorTest.php b/tests/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/ReplaceEventManagerWithEventSubscriberRectorTest.php new file mode 100644 index 0000000..d602128 --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/ReplaceEventManagerWithEventSubscriberRectorTest.php @@ -0,0 +1,24 @@ +doTestFileInfo($fixtureFileInfo); + $this->doTestExtraFile('Event/SomeClassCopyEvent.php', __DIR__ . '/Source/ExpectedSomeClassCopyEvent.php'); + } + + protected function getRectorClass(): string + { + return ReplaceEventManagerWithEventSubscriberRector::class; + } +} diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/Source/ExpectedSomeClassCopyEvent.php b/tests/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/Source/ExpectedSomeClassCopyEvent.php new file mode 100644 index 0000000..cbc2c6c --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceEventManagerWithEventSubscriberRector/Source/ExpectedSomeClassCopyEvent.php @@ -0,0 +1,22 @@ +someClass = $someClass; + $this->key = $key; + } + public function getSomeClass(): \Rector\Tests\NetteKdyby\Rector\MethodCall\ReplaceEventManagerWithEventSubscriberRector\Fixture\SomeClass + { + return $this->someClass; + } + public function getKey(): string + { + return $this->key; + } +} diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/duplicated_event_params.php.inc b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/duplicated_event_params.php.inc new file mode 100644 index 0000000..7879ab3 --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/duplicated_event_params.php.inc @@ -0,0 +1,38 @@ +onUpload($user['owner_id'], $user->name_value, $some_underscore); + } +} + +?> +----- +name_value, $some_underscore); + $this->eventDispatcher->dispatch($duplicatedEventParamsUploadEvent); + } +} + +?> diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/simple_event.php.inc b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/simple_event.php.inc new file mode 100644 index 0000000..87705db --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/simple_event.php.inc @@ -0,0 +1,38 @@ +onUpload($user); + } +} + +?> +----- +eventDispatcher->dispatch($fileManagerUploadEvent); + } +} + +?> diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/skip_on_success_in_control.php.inc b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/skip_on_success_in_control.php.inc new file mode 100644 index 0000000..0831b97 --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Fixture/skip_on_success_in_control.php.inc @@ -0,0 +1,20 @@ +onSuccess($user, 'run'); + + $this->onBeforeSuccess($user, 'run'); + } +} diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/ReplaceMagicPropertyEventWithEventClassRectorTest.php b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/ReplaceMagicPropertyEventWithEventClassRectorTest.php new file mode 100644 index 0000000..f8fca0f --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/ReplaceMagicPropertyEventWithEventClassRectorTest.php @@ -0,0 +1,55 @@ +doTestFileInfo($fixtureFileInfo); + } + + /** + * @dataProvider provideData() + */ + public function test( + SmartFileInfo $fixtureFileInfo, + string $expectedRelativeFilePath, + string $expectedContentFilePath + ): void { + $this->doTestFileInfo($fixtureFileInfo); + + $this->doTestExtraFile($expectedRelativeFilePath, $expectedContentFilePath); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + yield [ + new SmartFileInfo(__DIR__ . '/Fixture/simple_event.php.inc'), + '/Event/FileManagerUploadEvent.php', + __DIR__ . '/Source/ExpectedFileManagerUploadEvent.php', + ]; + + yield [ + new SmartFileInfo(__DIR__ . '/Fixture/duplicated_event_params.php.inc'), + '/Event/DuplicatedEventParamsUploadEvent.php', + __DIR__ . '/Source/ExpectedDuplicatedEventParamsUploadEvent.php', + ]; + } + + protected function getRectorClass(): string + { + return ReplaceMagicPropertyEventWithEventClassRector::class; + } +} diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedDuplicatedEventParamsUploadEvent.php b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedDuplicatedEventParamsUploadEvent.php new file mode 100644 index 0000000..1286fdd --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedDuplicatedEventParamsUploadEvent.php @@ -0,0 +1,34 @@ +userOwnerId = $userOwnerId; + $this->userNameValue = $userNameValue; + $this->someUnderscore = $someUnderscore; + } + public function getUserOwnerId() + { + return $this->userOwnerId; + } + public function getUserNameValue() + { + return $this->userNameValue; + } + public function getSomeUnderscore(): string + { + return $this->someUnderscore; + } +} diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedFileManagerUploadEvent.php b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedFileManagerUploadEvent.php new file mode 100644 index 0000000..4eff64a --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/ExpectedFileManagerUploadEvent.php @@ -0,0 +1,16 @@ +user = $user; + } + public function getUser(): \Rector\Tests\NetteKdyby\Rector\MethodCall\ReplaceMagicPropertyEventWithEventClassRector\Source\SomeUser + { + return $this->user; + } +} diff --git a/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/SomeUser.php b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/SomeUser.php new file mode 100644 index 0000000..ec1238f --- /dev/null +++ b/tests/NetteKdyby/Rector/MethodCall/ReplaceMagicPropertyEventWithEventClassRector/Source/SomeUser.php @@ -0,0 +1,10 @@ + +----- + diff --git a/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/assert_type.php.inc b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/assert_type.php.inc new file mode 100644 index 0000000..7b5af59 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/assert_type.php.inc @@ -0,0 +1,61 @@ + +----- +assertIsArray($value); + $this->assertIsArray($value); + $this->assertIsBool($value); + $this->assertIsCallable($value); + $this->assertIsFloat($value); + $this->assertIsInt($value); + $this->assertIsInt($value); + $this->assertNull($value); + $this->assertIsObject($value); + $this->assertIsResource($value); + $this->assertIsScalar($value); + $this->assertIsString($value); + $this->assertInstanceOf(\stdClass::class, $value); + } +} + +?> diff --git a/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/data_provider.php.inc b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/data_provider.php.inc new file mode 100644 index 0000000..110af88 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/data_provider.php.inc @@ -0,0 +1,107 @@ +setDatabasePlatform(new Doctrine\DBAL\Platforms\MySqlPlatform()); + $conn->throwOldKdybyExceptions = TRUE; + $resolved = $conn->resolveException($exception); + Assert::true($resolved instanceof $class); + foreach ($props as $prop => $val) { + Assert::same($val, $resolved->{$prop}); + } + } + /** + * @return array + */ + public function dataMySqlExceptions() + { + $e = new \PDOException('SQLSTATE[23000]: Integrity constraint violation: 1048 Column \'name\' cannot be null', '23000'); + $e->errorInfo = ['23000', 1048, 'Column \'name\' cannot be null']; + $emptyPdo = new PDOException($e); + $driver = new MysqlDriverMock(); + $empty = Doctrine\DBAL\DBALException::driverExceptionDuringQuery( + $driver, $emptyPdo, "INSERT INTO `test_empty` (`name`) VALUES (NULL)", [] + ); + $e = new \PDOException('SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'filip-prochazka\' for key \'uniq_name_surname\'', '23000'); + $e->errorInfo = ['23000', 1062, 'Duplicate entry \'filip-prochazka\' for key \'uniq_name_surname\'']; + $uniquePdo = new PDOException($e); + $unique = Doctrine\DBAL\DBALException::driverExceptionDuringQuery( + $driver, $uniquePdo, "INSERT INTO `test_empty` (`name`, `surname`) VALUES ('filip', 'prochazka')", [] + ); + return [ + [$empty, \Kdyby\Doctrine\EmptyValueException::class, ['column' => 'name']], + [$unique, \Kdyby\Doctrine\DuplicateEntryException::class, ['columns' => ['uniq_name_surname' => ['name', 'surname']]]], + ]; + } +} + +?> +----- +setDatabasePlatform(new Doctrine\DBAL\Platforms\MySqlPlatform()); + $conn->throwOldKdybyExceptions = TRUE; + $resolved = $conn->resolveException($exception); + $this->assertTrue($resolved instanceof $class); + foreach ($props as $prop => $val) { + $this->assertSame($val, $resolved->{$prop}); + } + } + /** + * @return array + */ + public function dataMySqlExceptions() + { + $e = new \PDOException('SQLSTATE[23000]: Integrity constraint violation: 1048 Column \'name\' cannot be null', '23000'); + $e->errorInfo = ['23000', 1048, 'Column \'name\' cannot be null']; + $emptyPdo = new PDOException($e); + $driver = new MysqlDriverMock(); + $empty = Doctrine\DBAL\DBALException::driverExceptionDuringQuery( + $driver, $emptyPdo, "INSERT INTO `test_empty` (`name`) VALUES (NULL)", [] + ); + $e = new \PDOException('SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'filip-prochazka\' for key \'uniq_name_surname\'', '23000'); + $e->errorInfo = ['23000', 1062, 'Duplicate entry \'filip-prochazka\' for key \'uniq_name_surname\'']; + $uniquePdo = new PDOException($e); + $unique = Doctrine\DBAL\DBALException::driverExceptionDuringQuery( + $driver, $uniquePdo, "INSERT INTO `test_empty` (`name`, `surname`) VALUES ('filip', 'prochazka')", [] + ); + return [ + [$empty, \Kdyby\Doctrine\EmptyValueException::class, ['column' => 'name']], + [$unique, \Kdyby\Doctrine\DuplicateEntryException::class, ['columns' => ['uniq_name_surname' => ['name', 'surname']]]], + ]; + } +} + +?> diff --git a/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/kdyby_tests_events.php.inc b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/kdyby_tests_events.php.inc new file mode 100644 index 0000000..c7e3049 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/kdyby_tests_events.php.inc @@ -0,0 +1,337 @@ +em = $this->createMemoryManagerWithSchema([ + __DIR__ . '/config/events.neon', + ]); + } + + public function testOuterRegister_new() + { + Assert::type(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + $outerEvm = $this->em->getEventManager(); + Assert::false($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->addEventSubscriber($new = new NewListener()); + + Assert::true($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + Assert::same([[$args]], $new->calls); + } + + + + public function testOuterRegister_old() + { + Assert::type(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + $outerEvm = $this->em->getEventManager(); + Assert::false($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->addEventSubscriber($old = new OldListener()); + + Assert::true($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + Assert::same([[$args]], $old->calls); + } + + + + public function testOuterRegister_combined() + { + Assert::type(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + $outerEvm = $this->em->getEventManager(); + Assert::false($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->addEventSubscriber($old = new OldListener()); + $outerEvm->addEventSubscriber($new = new NewListener()); + + Assert::true($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + Assert::same([[$args]], $old->calls); + Assert::same([[$args]], $new->calls); + } + + + + public function testInnerRegister_new() + { + Assert::type(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + /** @var Kdyby\Events\EventManager $innerEvm */ + $innerEvm = $this->serviceLocator->getByType(\Kdyby\Events\EventManager::class); + Assert::false($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm = $this->em->getEventManager(); + Assert::false($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $innerEvm->addEventSubscriber($new = new NewListener()); + + Assert::false($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + Assert::same([[$args]], $new->calls); + } + + + + public function testInnerRegister_old() + { + Assert::type(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + /** @var Kdyby\Events\EventManager $innerEvm */ + $innerEvm = $this->serviceLocator->getByType(\Kdyby\Events\EventManager::class); + Assert::false($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm = $this->em->getEventManager(); + Assert::false($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $innerEvm->addEventSubscriber($old = new OldListener()); + + Assert::true($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + Assert::same([[$args]], $old->calls); + } + + + + public function testInnerRegister_combined() + { + Assert::type(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + /** @var Kdyby\Events\EventManager $innerEvm */ + $innerEvm = $this->serviceLocator->getByType(\Kdyby\Events\EventManager::class); + Assert::false($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm = $this->em->getEventManager(); + Assert::false($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::false($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $innerEvm->addEventSubscriber($old = new OldListener()); + $innerEvm->addEventSubscriber($new = new NewListener()); + + Assert::true($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + Assert::true($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + Assert::same([[$args]], $old->calls); + Assert::same([[$args]], $new->calls); + } + +} + +(new EventsCompatibilityTest())->run(); + +?> +----- +em = $this->createMemoryManagerWithSchema([ + __DIR__ . '/config/events.neon', + ]); + } + + public function testOuterRegister_new() + { + $this->assertInstanceOf(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + $outerEvm = $this->em->getEventManager(); + $this->assertFalse($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->addEventSubscriber($new = new NewListener()); + + $this->assertTrue($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + $this->assertSame([[$args]], $new->calls); + } + + + + public function testOuterRegister_old() + { + $this->assertInstanceOf(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + $outerEvm = $this->em->getEventManager(); + $this->assertFalse($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->addEventSubscriber($old = new OldListener()); + + $this->assertTrue($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + $this->assertSame([[$args]], $old->calls); + } + + + + public function testOuterRegister_combined() + { + $this->assertInstanceOf(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + $outerEvm = $this->em->getEventManager(); + $this->assertFalse($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->addEventSubscriber($old = new OldListener()); + $outerEvm->addEventSubscriber($new = new NewListener()); + + $this->assertTrue($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + $this->assertSame([[$args]], $old->calls); + $this->assertSame([[$args]], $new->calls); + } + + + + public function testInnerRegister_new() + { + $this->assertInstanceOf(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + /** @var Kdyby\Events\EventManager $innerEvm */ + $innerEvm = $this->serviceLocator->getByType(\Kdyby\Events\EventManager::class); + $this->assertFalse($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm = $this->em->getEventManager(); + $this->assertFalse($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $innerEvm->addEventSubscriber($new = new NewListener()); + + $this->assertFalse($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + $this->assertSame([[$args]], $new->calls); + } + + + + public function testInnerRegister_old() + { + $this->assertInstanceOf(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + /** @var Kdyby\Events\EventManager $innerEvm */ + $innerEvm = $this->serviceLocator->getByType(\Kdyby\Events\EventManager::class); + $this->assertFalse($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm = $this->em->getEventManager(); + $this->assertFalse($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $innerEvm->addEventSubscriber($old = new OldListener()); + + $this->assertTrue($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + $this->assertSame([[$args]], $old->calls); + } + + + + public function testInnerRegister_combined() + { + $this->assertInstanceOf(\Kdyby\Events\NamespacedEventManager::class, $this->em->getEventManager()); + + /** @var Kdyby\Events\EventManager $innerEvm */ + $innerEvm = $this->serviceLocator->getByType(\Kdyby\Events\EventManager::class); + $this->assertFalse($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm = $this->em->getEventManager(); + $this->assertFalse($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertFalse($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $innerEvm->addEventSubscriber($old = new OldListener()); + $innerEvm->addEventSubscriber($new = new NewListener()); + + $this->assertTrue($innerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($innerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Doctrine\ORM\Events::onFlush)); + $this->assertTrue($outerEvm->hasListeners(Kdyby\Doctrine\Events::onFlush)); + + $outerEvm->dispatchEvent(Doctrine\ORM\Events::onFlush, $args = new OnFlushEventArgs($this->em)); + + $this->assertSame([[$args]], $old->calls); + $this->assertSame([[$args]], $new->calls); + } + +} + +?> diff --git a/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/test_class.php.inc b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/test_class.php.inc new file mode 100644 index 0000000..0b1e334 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/test_class.php.inc @@ -0,0 +1,61 @@ +say(''); + }, InvalidArgumentException::class, 'Invalid name'); + } +} + +(new ExtensionTest())->run(); + +?> +----- +assertFalse(5); + } + + public function testException() + { + $o = 1; + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid name'); + $o->say(''); + } +} + +?> diff --git a/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/various_asserts.php.inc b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/various_asserts.php.inc new file mode 100644 index 0000000..304dd63 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Fixture/various_asserts.php.inc @@ -0,0 +1,101 @@ +run(); + }, 'ExceptionClass', "Service 'one': Class or interface 'X' not found.", 200); + } + + public function testNoError() + { + Assert::noError(function () { + $value = 1; + }); + } + + public function testY() + { + Assert::falsey('value', 'some messsage'); + // keep my comments + Assert::truthy(true); + } + + public function testContains() + { + $value = 'some messsage'; + Assert::contains('value', $value); + $values = []; + Assert::contains('value', $values); + } +} + +?> +----- +assertInstanceOf(\Kdyby\Doctrine\EntityManager::class, $value); + $this->assertFalse(5); + $this->assertSame('ExpectedValue', $value); + } + + public function testExceptions() + { + $this->expectException('ExceptionClass'); + $this->expectExceptionMessage("Service 'one': Class or interface 'X' not found."); + $this->expectExceptionCode(200); + $builder = new DI\ContainerBuilder; + $builder->run(); + } + + /** + * @doesNotPerformAssertions + */ + public function testNoError() + { + $value = 1; + } + + public function testY() + { + $this->assertFalse((bool) 'value', 'some messsage'); + // keep my comments + $this->assertTrue(true); + } + + public function testContains() + { + $value = 'some messsage'; + $this->assertStringContainsString('value', $value); + $values = []; + $this->assertContains('value', $values); + } +} + +?> diff --git a/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/NetteTesterClassToPHPUnitClassRectorTest.php b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/NetteTesterClassToPHPUnitClassRectorTest.php new file mode 100644 index 0000000..6f2ce8a --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/NetteTesterClassToPHPUnitClassRectorTest.php @@ -0,0 +1,33 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Source/ORMTestCase.php b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Source/ORMTestCase.php new file mode 100644 index 0000000..4ba8e42 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/Class_/NetteTesterClassToPHPUnitClassRector/Source/ORMTestCase.php @@ -0,0 +1,11 @@ +services(); + $services->set(NetteAssertToPHPUnitAssertRector::class); + $services->set(NetteTesterClassToPHPUnitClassRector::class); +}; diff --git a/tests/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector/RenameTesterTestToPHPUnitToTestFileRectorTest.php b/tests/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector/RenameTesterTestToPHPUnitToTestFileRectorTest.php new file mode 100644 index 0000000..c6ceb25 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector/RenameTesterTestToPHPUnitToTestFileRectorTest.php @@ -0,0 +1,45 @@ +doTestFileInfo($fixtureFileInfo); + $this->assertFileWithContentWasAdded($expectedAddedFileWithContent); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + $smartFileSystem = new SmartFileSystem(); + + yield [ + new SmartFileInfo(__DIR__ . '/Source/SomeCase.phpt'), + new AddedFileWithContent( + $this->getFixtureTempDirectory() . '/Source/SomeCaseTest.php', + $smartFileSystem->readFile(__DIR__ . '/Source/SomeCase.phpt') + ), + ]; + } + + protected function getRectorClass(): string + { + return RenameTesterTestToPHPUnitToTestFileRector::class; + } +} diff --git a/tests/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector/Source/SomeCase.phpt b/tests/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector/Source/SomeCase.phpt new file mode 100644 index 0000000..7529844 --- /dev/null +++ b/tests/NetteTesterToPHPUnit/Rector/FileNode/RenameTesterTestToPHPUnitToTestFileRector/Source/SomeCase.phpt @@ -0,0 +1,12 @@ + 'someMethod', + ]; + } + + public function someMethod(OldEvent $event) + { + } +} + +?> +----- + 'someMethod', + ]; + } + + public function someMethod(\Symfony\Component\HttpKernel\Event\GetResponseEvent $event) + { + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/Fixture/fixture.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..a80c6b5 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/Fixture/fixture.php.inc @@ -0,0 +1,39 @@ + 'someMethod', + \Contributte\Events\Extra\Event\Application\StartupEvent::NAME => 'someMethod', + ]; + } +} + +?> +----- + 'someMethod', + \Symfony\Component\HttpKernel\KernelEvents::REQUEST => 'someMethod', + ]; + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/Fixture/presenter_startup.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/Fixture/presenter_startup.php.inc new file mode 100644 index 0000000..b56fc23 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/Fixture/presenter_startup.php.inc @@ -0,0 +1,37 @@ + 'someMethod', + ]; + } +} + +?> +----- + 'someMethod', + ]; + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/RenameEventNamesInEventSubscriberRectorTest.php b/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/RenameEventNamesInEventSubscriberRectorTest.php new file mode 100644 index 0000000..b36a005 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RenameEventNamesInEventSubscriberRector/RenameEventNamesInEventSubscriberRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return RenameEventNamesInEventSubscriberRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/constant_reference_route_to_annotation.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/constant_reference_route_to_annotation.php.inc new file mode 100644 index 0000000..0411a87 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/constant_reference_route_to_annotation.php.inc @@ -0,0 +1,61 @@ + +----- + diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/general_method_named_routes.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/general_method_named_routes.php.inc new file mode 100644 index 0000000..c51787c --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/general_method_named_routes.php.inc @@ -0,0 +1,66 @@ +/', 'Homepage:default'); + + return $routeList; + } +} + +final class GeneralMethodNamedRoutesSomePresenter +{ + public function actionFirst() + { + } + + public function actionSecond() + { + } +} + +?> +----- + diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/method_named_routes.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/method_named_routes.php.inc new file mode 100644 index 0000000..4249526 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/method_named_routes.php.inc @@ -0,0 +1,68 @@ +/', 'Homepage:default'); + + return $routeList; + } +} + +final class MethodNamedRoutesSomePresenter +{ + public function actionFirst() + { + } + + public function actionSecond() + { + } +} + +?> +----- + diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/new_route_to_annotation.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/new_route_to_annotation.php.inc new file mode 100644 index 0000000..f1f546f --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/new_route_to_annotation.php.inc @@ -0,0 +1,57 @@ + +----- + diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/static_route_to_annotation.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/static_route_to_annotation.php.inc new file mode 100644 index 0000000..8fded5d --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/static_route_to_annotation.php.inc @@ -0,0 +1,57 @@ + +----- + diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/with_parameter.php.inc b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/with_parameter.php.inc new file mode 100644 index 0000000..4fc5a16 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Fixture/with_parameter.php.inc @@ -0,0 +1,64 @@ +', WithParameterSomePresenter::class); + + return $routeList; + } +} + +final class WithParameterSomePresenter implements IPresenter +{ + /** + * @doto + */ + public function run(\Nette\Application\Request $request): IResponse + { + } +} + +?> +----- + diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/RouterListToControllerAnnotationsRectorTest.php b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/RouterListToControllerAnnotationsRectorTest.php new file mode 100644 index 0000000..5143025 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/RouterListToControllerAnnotationsRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return RouterListToControllerAnnotationsRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Source/Route.php b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Source/Route.php new file mode 100644 index 0000000..097f378 --- /dev/null +++ b/tests/NetteToSymfony/Rector/ClassMethod/RouterListToControllerAnnotationsRector/Source/Route.php @@ -0,0 +1,12 @@ +addText('name', 'Your name'); + + $form->onSuccess[] = [$this, 'processForm']; + } + + public function processForm(Form $form) + { + // process me + } +} + +?> +----- +add('name', \Symfony\Component\Form\Extension\Core\Type\TextType::class, ['label' => 'Your name']); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector/FormControlToControllerAndFormTypeRectorTest.php b/tests/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector/FormControlToControllerAndFormTypeRectorTest.php new file mode 100644 index 0000000..1dd7f5f --- /dev/null +++ b/tests/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector/FormControlToControllerAndFormTypeRectorTest.php @@ -0,0 +1,42 @@ +doTestFileInfo($fileInfo); + $this->doTestExtraFile($expectedExtraFileName, $expectedExtraContentFilePath); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + yield [ + new SmartFileInfo(__DIR__ . '/Fixture/fixture.php.inc'), + 'src/Controller/SomeFormController.php', + __DIR__ . '/Source/extra_file.php', + ]; + } + + protected function getRectorClass(): string + { + return FormControlToControllerAndFormTypeRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector/Source/extra_file.php b/tests/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector/Source/extra_file.php new file mode 100644 index 0000000..defdab2 --- /dev/null +++ b/tests/NetteToSymfony/Rector/Class_/FormControlToControllerAndFormTypeRector/Source/extra_file.php @@ -0,0 +1,14 @@ +createForm(\Rector\Tests\NetteToSymfony\Rector\Class_\FormControlToControllerAndFormTypeRector\Fixture\SomeFormType::class); + $form->handleRequest($request); + if ($form->isSuccess() && $form->isValid()) { + } + } +} diff --git a/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/fixture.php.inc b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..6f668ee --- /dev/null +++ b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/fixture.php.inc @@ -0,0 +1,32 @@ +template->param = 'some value'; + $this->template->render(__DIR__ . '/poll.latte'); + } +} + +?> +----- +render(__DIR__ . '/poll.latte', ['param' => 'some value']); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/get_presenter_session.php.inc b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/get_presenter_session.php.inc new file mode 100644 index 0000000..c387915 --- /dev/null +++ b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/get_presenter_session.php.inc @@ -0,0 +1,42 @@ +getPresenter()->getSession('some_name'); + + $this->render(); + } +} + +?> +----- +session->getSession('some_name'); + + return $this->render(); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/skip_presenter.php.inc b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/skip_presenter.php.inc new file mode 100644 index 0000000..125064a --- /dev/null +++ b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/skip_presenter.php.inc @@ -0,0 +1,14 @@ +template->param = 'some value'; + $this->template->render(__DIR__ . '/poll.latte'); + } +} diff --git a/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/template_with_assign.php.inc b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/template_with_assign.php.inc new file mode 100644 index 0000000..b927237 --- /dev/null +++ b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/Fixture/template_with_assign.php.inc @@ -0,0 +1,62 @@ +basketManager = $basketManager; + } + + public function render(): void + { + $this->template->setFile(__DIR__ . '/templates/default.latte'); + + $this->template->items = $this->basketManager->getItems(); + $this->template->discountCode = $this->basketManager->getDiscountCode(); + + $this->template->render(); + } +} + +?> +----- +basketManager = $basketManager; + } + + public function action(): \Symfony\Component\HttpFoundation\Response + { + return $this->render(__DIR__ . '/templates/default.latte', ['items' => $this->basketManager->getItems(), 'discountCode' => $this->basketManager->getDiscountCode()]); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/NetteControlToSymfonyControllerRectorTest.php b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/NetteControlToSymfonyControllerRectorTest.php new file mode 100644 index 0000000..c536b5c --- /dev/null +++ b/tests/NetteToSymfony/Rector/Class_/NetteControlToSymfonyControllerRector/NetteControlToSymfonyControllerRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return NetteControlToSymfonyControllerRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector/DeleteFactoryInterfaceFileSystemRectorTest.php b/tests/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector/DeleteFactoryInterfaceFileSystemRectorTest.php new file mode 100644 index 0000000..a778268 --- /dev/null +++ b/tests/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector/DeleteFactoryInterfaceFileSystemRectorTest.php @@ -0,0 +1,35 @@ +doTestFileInfo($smartFileInfo); + $this->assertFileWasRemoved($this->originalTempFileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return DeleteFactoryInterfaceRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector/Fixture/SkipSomeFactoryInterface.php.inc b/tests/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector/Fixture/SkipSomeFactoryInterface.php.inc new file mode 100644 index 0000000..afce985 --- /dev/null +++ b/tests/NetteToSymfony/Rector/Interface_/DeleteFactoryInterfaceRector/Fixture/SkipSomeFactoryInterface.php.inc @@ -0,0 +1,15 @@ +httpRequest->getHeader('x'); + } +} + +?> +----- +headers->get('x'); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/Fixture/missing_argument.php.inc b/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/Fixture/missing_argument.php.inc new file mode 100644 index 0000000..da95d5e --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/Fixture/missing_argument.php.inc @@ -0,0 +1,53 @@ +httpRequest->getHeader('x'); + } + + public function anotherAction(IrrelevantRequest $request) + { + $header = $this->httpRequest->getHeader('x'); + } +} + +?> +----- +headers->get('x'); + } + + public function anotherAction(IrrelevantRequest $request, \Symfony\Component\HttpFoundation\Request $symfonyRequest) + { + $header = $symfonyRequest->headers->get('x'); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/FromHttpRequestGetHeaderToHeadersGetRectorTest.php b/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/FromHttpRequestGetHeaderToHeadersGetRectorTest.php new file mode 100644 index 0000000..dfb4ac3 --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/FromHttpRequestGetHeaderToHeadersGetRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return FromHttpRequestGetHeaderToHeadersGetRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/Source/IrrelevantRequest.php b/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/Source/IrrelevantRequest.php new file mode 100644 index 0000000..3060665 --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/FromHttpRequestGetHeaderToHeadersGetRector/Source/IrrelevantRequest.php @@ -0,0 +1,10 @@ +getParameter('abz'); + } + + public function anotherAction(Request $appRequest) + { + $value = $appRequest->getParameter('abz'); + } +} + +?> +----- +attributes->get('abz'); + } + + public function anotherAction(Request $appRequest) + { + $value = $appRequest->attributes->get('abz'); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/MethodCall/FromRequestGetParameterToAttributesGetRector/FromRequestGetParameterToAttributesGetRectorTest.php b/tests/NetteToSymfony/Rector/MethodCall/FromRequestGetParameterToAttributesGetRector/FromRequestGetParameterToAttributesGetRectorTest.php new file mode 100644 index 0000000..5d8031c --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/FromRequestGetParameterToAttributesGetRector/FromRequestGetParameterToAttributesGetRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return FromRequestGetParameterToAttributesGetRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/Fixture/fixture.php.inc b/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/Fixture/fixture.php.inc new file mode 100644 index 0000000..e4512c4 --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/Fixture/fixture.php.inc @@ -0,0 +1,67 @@ +addText('name', 'Name:'); + // "name", "label", "cols", "maxLength" + $form->addText('name', 'Name:', 25, 200); + + $form->addPassword('password', 'Password:'); + $form->addSubmit('login', 'Sign up'); + } + + public function selects() + { + $form = new Form; + $form->addSelect('select'); + $form->addRadioList('radio_list'); + $form->addCheckboxList('checkbox_list'); + $form->addMultiSelect('multi_select'); + + $form->addMultiUpload('multi_upload'); + } +} + +?> +----- +createFormBuilder(); + $form->add('name', \Symfony\Component\Form\Extension\Core\Type\TextType::class, ['label' => 'Name:']); + // "name", "label", "cols", "maxLength" + $form->add('name', \Symfony\Component\Form\Extension\Core\Type\TextType::class, ['label' => 'Name:']); + + $form->add('password', \Symfony\Component\Form\Extension\Core\Type\PasswordType::class, ['label' => 'Password:']); + $form->add('login', \Symfony\Component\Form\Extension\Core\Type\SubmitType::class, ['label' => 'Sign up']); + } + + public function selects() + { + $form = $this->createFormBuilder(); + $form->add('select', \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class, ['expanded' => false, 'multiple' => false]); + $form->add('radio_list', \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class, ['expanded' => true, 'multiple' => false]); + $form->add('checkbox_list', \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class, ['expanded' => true, 'multiple' => true]); + $form->add('multi_select', \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class, ['expanded' => false, 'multiple' => true]); + + $form->add('multi_upload', \Symfony\Component\Form\Extension\Core\Type\FileType::class, ['multiple' => true]); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/NetteFormToSymfonyFormRectorTest.php b/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/NetteFormToSymfonyFormRectorTest.php new file mode 100644 index 0000000..49f5a28 --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/NetteFormToSymfonyFormRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return NetteFormToSymfonyFormRector::class; + } +} diff --git a/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/Source/NettePresenter.php b/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/Source/NettePresenter.php new file mode 100644 index 0000000..3919a8f --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/NetteFormToSymfonyFormRector/Source/NettePresenter.php @@ -0,0 +1,17 @@ +trans( + 'Hello %name%', + ['name' => 'Tom'] + ); + } +} + +?> +----- +trans( + 'Hello %name%', + ['%name%' => 'Tom'] + ); + } +} + +?> diff --git a/tests/NetteToSymfony/Rector/MethodCall/WrapTransParameterNameRector/WrapTransParameterNameRectorTest.php b/tests/NetteToSymfony/Rector/MethodCall/WrapTransParameterNameRector/WrapTransParameterNameRectorTest.php new file mode 100644 index 0000000..5653398 --- /dev/null +++ b/tests/NetteToSymfony/Rector/MethodCall/WrapTransParameterNameRector/WrapTransParameterNameRectorTest.php @@ -0,0 +1,34 @@ +doTestFileInfo($fileInfo); + } + + /** + * @return Iterator + */ + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return WrapTransParameterNameRector::class; + } +}