Browse Source

Add rector

Laurent Destailleur 1 year ago
parent
commit
1930ea81a9

+ 0 - 0
dev/fixdosfiles.sh → dev/tools/fixdosfiles.sh


+ 2 - 0
dev/tools/rector/.gitignore

@@ -0,0 +1,2 @@
+/test.php
+/vendor

+ 27 - 0
dev/tools/rector/README.md

@@ -0,0 +1,27 @@
+### Refactoring code with [rector](https://getrector.com)
+
+
+#### Installation
+
+Run in this folder
+```shell
+cd dev/tools/rector
+```
+
+Install rector with composer
+```shell
+composer install
+```
+
+#### Usage
+
+##### To make changes (Add --dry-run for test mode only)
+```shell
+./vendor/bin/rector process --dry-run
+```
+
+##### To make changes on a given directory
+
+```shell
+./vendor/bin/rector process [--dry-run] [--clear-cache] ../../../htdocs/core/
+```

+ 19 - 0
dev/tools/rector/composer.json

@@ -0,0 +1,19 @@
+{
+  "name": "dolibarr/rector",
+  "type": "project",
+  "license": "GplV3",
+  "authors": [
+    {
+      "name": "Dev2a",
+      "email": "contact@dev2a.pro"
+    }
+  ],
+  "require-dev": {
+    "rector/rector": "^0.18.5"
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Dolibarr\\Rector\\": "./src"
+    }
+  }
+}

+ 137 - 0
dev/tools/rector/composer.lock

@@ -0,0 +1,137 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "f2998987cad52db5ab60d5ff0672ce05",
+    "packages": [],
+    "packages-dev": [
+        {
+            "name": "phpstan/phpstan",
+            "version": "1.10.38",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpstan/phpstan.git",
+                "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691",
+                "reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2|^8.0"
+            },
+            "conflict": {
+                "phpstan/phpstan-shim": "*"
+            },
+            "bin": [
+                "phpstan",
+                "phpstan.phar"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "PHPStan - PHP Static Analysis Tool",
+            "keywords": [
+                "dev",
+                "static analysis"
+            ],
+            "support": {
+                "docs": "https://phpstan.org/user-guide/getting-started",
+                "forum": "https://github.com/phpstan/phpstan/discussions",
+                "issues": "https://github.com/phpstan/phpstan/issues",
+                "security": "https://github.com/phpstan/phpstan/security/policy",
+                "source": "https://github.com/phpstan/phpstan-src"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/ondrejmirtes",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/phpstan",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-10-06T14:19:14+00:00"
+        },
+        {
+            "name": "rector/rector",
+            "version": "0.18.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/rectorphp/rector.git",
+                "reference": "2a3b82f317e431fc142d21f3303891a4e64c96eb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/rectorphp/rector/zipball/2a3b82f317e431fc142d21f3303891a4e64c96eb",
+                "reference": "2a3b82f317e431fc142d21f3303891a4e64c96eb",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2|^8.0",
+                "phpstan/phpstan": "^1.10.35"
+            },
+            "conflict": {
+                "rector/rector-doctrine": "*",
+                "rector/rector-downgrade-php": "*",
+                "rector/rector-phpunit": "*",
+                "rector/rector-symfony": "*"
+            },
+            "bin": [
+                "bin/rector"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Instant Upgrade and Automated Refactoring of any PHP code",
+            "keywords": [
+                "automation",
+                "dev",
+                "migration",
+                "refactoring"
+            ],
+            "support": {
+                "issues": "https://github.com/rectorphp/rector/issues",
+                "source": "https://github.com/rectorphp/rector/tree/0.18.5"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/tomasvotruba",
+                    "type": "github"
+                }
+            ],
+            "time": "2023-10-05T11:25:40+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "2.2.0"
+}

+ 53 - 0
dev/tools/rector/rector.php

@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+use Rector\Config\RectorConfig;
+use Rector\Core\ValueObject\PhpVersion;
+use Rector\Set\ValueObject\LevelSetList;
+
+return static function (RectorConfig $rectorConfig): void {
+	$rectorConfig->phpVersion(PhpVersion::PHP_71);
+	//$rectorConfig->indent(' ', 4);
+
+	// Traits seems not supported correctly by rector without declaring them as bootstrapFiles
+	$arrayoftraitfiles = array(
+		__DIR__ . '/../../../htdocs/core/class/commonincoterm.class.php',
+		__DIR__ . '/../../../htdocs/core/class/commonpeople.class.php',
+		__DIR__ . '/../../../htdocs/core/class/commonsocialnetworks.class.php'
+	);
+	$rectorConfig->bootstrapFiles($arrayoftraitfiles);
+
+	$rectorConfig->paths([
+		__DIR__ . '/../../../htdocs/',
+		__DIR__ . '/../../../scripts/',
+		__DIR__ . '/../../../test/phpunit/',
+	]);
+	$rectorConfig->skip([
+		'**/includes/**',
+		'**/custom/**',
+		__DIR__ . '/../../../htdocs/custom/',
+		__DIR__ . '/../../../htdocs/install/doctemplates/*'
+	]);
+	$rectorConfig->parallel(240);
+
+
+	// Register rules
+
+	//$rectorConfig->rule(Rector\Php71\Rector\List_\ListToArrayDestructRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\GetClassOnNullRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\Assign\ListEachRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\ParseStrWithResultArgumentRector::class);
+	//$rectorConfig->rule(ReplaceEachAssignmentWithKeyCurrentRector::class);
+	//$rectorConfig->rule(Rector\Php72\Rector\FuncCall\StringifyDefineRector::class);
+
+	//$rectorConfig->rule(Dolibarr\Rector\Renaming\GlobalToFunction::class);
+	//$rectorConfig->rule(Dolibarr\Rector\Renaming\UserRightsToFunction::class);
+	$rectorConfig->rule(Dolibarr\Rector\Renaming\EmptyGlobalToFunction::class);
+
+	// Add all predefined rules to migrate to up to php 71
+	// $rectorConfig->sets([
+	//	LevelSetList::UP_TO_PHP_71
+	// ]);
+};

+ 214 - 0
dev/tools/rector/src/Renaming/EmptyGlobalToFunction.php

@@ -0,0 +1,214 @@
+<?php
+
+namespace Dolibarr\Rector\Renaming;
+
+use PhpParser\Node;
+use PhpParser\Node\Arg;
+use PhpParser\Node\Expr\ArrayDimFetch;
+use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
+use PhpParser\Node\Expr\BinaryOp\Concat;
+use PhpParser\Node\Expr\BinaryOp\Equal;
+use PhpParser\Node\Expr\BooleanNot;
+use PhpParser\Node\Expr\Empty_;
+use PhpParser\Node\Expr\FuncCall;
+use PhpParser\Node\Expr\Isset_;
+use PhpParser\Node\Expr\PropertyFetch;
+use PhpParser\Node\Name;
+use PhpParser\Node\Scalar\String_;
+use Rector\Core\NodeManipulator\BinaryOpManipulator;
+use Rector\Core\Rector\AbstractRector;
+use Rector\Php71\ValueObject\TwoNodeMatch;
+use Symplify\RuleDocGenerator\Exception\PoorDocumentationException;
+use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
+use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
+use Rector\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector;
+
+/**
+ * Class with Rector custom rule to fix code
+ */
+class EmptyGlobalToFunction extends AbstractRector
+{
+	/**
+	 * @var \Rector\Core\NodeManipulator\BinaryOpManipulator
+	 */
+	private $binaryOpManipulator;
+
+	/**
+	 * Constructor
+	 *
+	 * @param BinaryOpManipulator $binaryOpManipulator The $binaryOpManipulator
+	 */
+	public function __construct(BinaryOpManipulator $binaryOpManipulator)
+	{
+		$this->binaryOpManipulator = $binaryOpManipulator;
+	}
+
+	/**
+	 * getRuleDefinition
+	 *
+	 * @return RuleDefinition
+	 * @throws PoorDocumentationException
+	 */
+	public function getRuleDefinition(): RuleDefinition
+	{
+		return new RuleDefinition(
+			'Change $conf->global to getDolGlobal',
+			[new CodeSample('$conf->global->CONSTANT',
+				'getDolGlobalInt(\'CONSTANT\')'
+				)]);
+	}
+
+	/**
+	 * Return a node type from https://github.com/rectorphp/php-parser-nodes-docs/
+	 *
+	 * @return string[]
+	 */
+	public function getNodeTypes(): array
+	{
+		return [Node\Expr\BooleanNot::class, Node\Expr\Empty_::class];
+	}
+
+	/**
+	 * refactor
+	 *
+	 * @param 	Node 					$node 		A node
+	 * @return	FuncCall|BooleanNot
+	 */
+	public function refactor(Node $node)
+	{
+		if ($node instanceof Node\Expr\BooleanNot) {
+			if (!$node->expr instanceof Node\Expr\Empty_) {
+				return null;
+			}
+			// node is !empty(...) so we set node to ...
+			$newnode = $node->expr->expr;
+
+			$tmpglobal = $newnode->var;
+			if (is_null($tmpglobal)) {
+				return null;
+			}
+			if (!$this->isName($tmpglobal, 'global')) {
+				return null;
+			}
+
+			$tmpconf = $tmpglobal->var;
+			if (!$this->isName($tmpconf, 'conf')) {
+				return null;
+			}
+
+			$nameforconst = $this->getName($newnode);
+			if (is_null($nameforconst)) {
+				return null;
+			}
+			$constName = new String_($nameforconst);
+
+			// We found a node !empty(conf->global->XXX)
+			return new FuncCall(
+				new Name('getDolGlobalString'),
+				[new Arg($constName)]
+				);
+		}
+
+
+		if ($node instanceof Node\Expr\Empty_) {
+			// node is empty(...) so we set node to ...
+			$newnode = $node->expr;
+
+			$tmpglobal = $newnode->var;
+			if (is_null($tmpglobal)) {
+				return null;
+			}
+			if (!$this->isName($tmpglobal, 'global')) {
+				return null;
+			}
+
+			$tmpconf = $tmpglobal->var;
+			if (!$this->isName($tmpconf, 'conf')) {
+				return null;
+			}
+
+			$nameforconst = $this->getName($newnode);
+			if (is_null($nameforconst)) {
+				return null;
+			}
+			$constName = new String_($nameforconst);
+
+			return new BooleanNot(new FuncCall(
+				new Name('getDolGlobalString'),
+				[new Arg($constName)]
+				));
+		}
+
+		return null;
+	}
+
+	/**
+	 * Get nodes with check empty
+	 *
+	 * @param BooleanAnd $booleanAnd A BooleandAnd
+	 * @return    TwoNodeMatch|null
+	 */
+	private function resolveTwoNodeMatch(BooleanAnd $booleanAnd): ?TwoNodeMatch
+	{
+		return $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
+			$booleanAnd,
+			// $conf->global == $value
+			function (Node $node): bool {
+				if (!$node instanceof Equal) {
+					return \false;
+				}
+				return $this->isGlobalVar($node->left);
+			},
+			// !empty(...) || isset(...)
+			function (Node $node): bool {
+				if ($node instanceof BooleanNot && $node->expr instanceof Empty_) {
+					return $this->isGlobalVar($node->expr->expr);
+				}
+				if (!$node instanceof Isset_) {
+					return $this->isGlobalVar($node);
+				}
+				return \true;
+			}
+			);
+	}
+
+	/**
+	 * Check if node is a global access with format conf->global->XXX
+	 *
+	 * @param Node 	$node 	A node
+	 * @return bool			Return true if noe is conf->global->XXX
+	 */
+	private function isGlobalVar($node)
+	{
+		if (!$node instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($node->var, 'global')) {
+			return false;
+		}
+		$global = $node->var;
+		if (!$global instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($global->var, 'conf')) {
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * @param 	Node 		$node 	Node to be parsed
+	 * @return 	Node|void			Return the name of the constant
+	 */
+	private function getConstName($node)
+	{
+		if ($node instanceof PropertyFetch && $node->name instanceof Node\Expr) {
+			return $node->name;
+		}
+		$name = $this->getName($node);
+		if (empty($name)) {
+			return;
+		}
+		return new String_($name);
+	}
+}

+ 293 - 0
dev/tools/rector/src/Renaming/GlobalToFunction.php

@@ -0,0 +1,293 @@
+<?php
+
+namespace Dolibarr\Rector\Renaming;
+
+use PhpParser\Node;
+use PhpParser\Node\Arg;
+use PhpParser\Node\Expr\ArrayDimFetch;
+use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
+use PhpParser\Node\Expr\BinaryOp\Concat;
+use PhpParser\Node\Expr\BinaryOp\Equal;
+use PhpParser\Node\Expr\BooleanNot;
+use PhpParser\Node\Expr\Empty_;
+use PhpParser\Node\Expr\FuncCall;
+use PhpParser\Node\Expr\Isset_;
+use PhpParser\Node\Expr\PropertyFetch;
+use PhpParser\Node\Name;
+use PhpParser\Node\Scalar\String_;
+use Rector\Core\NodeManipulator\BinaryOpManipulator;
+use Rector\Core\Rector\AbstractRector;
+use Rector\Php71\ValueObject\TwoNodeMatch;
+use Symplify\RuleDocGenerator\Exception\PoorDocumentationException;
+use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
+use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
+use PhpParser\Node\Expr\BinaryOp\Greater;
+use PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
+use PhpParser\Node\Expr\BinaryOp\Smaller;
+use PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
+
+/**
+ * Class with Rector custom rule to fix code
+ */
+class GlobalToFunction extends AbstractRector
+{
+	/**
+	 * @var \Rector\Core\NodeManipulator\BinaryOpManipulator
+	 */
+	private $binaryOpManipulator;
+
+	/**
+	 * Constructor
+	 *
+	 * @param BinaryOpManipulator $binaryOpManipulator The $binaryOpManipulator
+	 */
+	public function __construct(BinaryOpManipulator $binaryOpManipulator)
+	{
+		$this->binaryOpManipulator = $binaryOpManipulator;
+	}
+
+	/**
+	 * getRuleDefinition
+	 *
+	 * @return RuleDefinition
+	 * @throws PoorDocumentationException
+	 */
+	public function getRuleDefinition(): RuleDefinition
+	{
+		return new RuleDefinition(
+			'Change $conf->global to getDolGlobal',
+			[new CodeSample('$conf->global->CONSTANT',
+				'getDolGlobalInt(\'CONSTANT\')'
+			)]);
+	}
+
+	/**
+	 * Return a node type from https://github.com/rectorphp/php-parser-nodes-docs/
+	 *
+	 * @return string[]
+	 */
+	public function getNodeTypes(): array
+	{
+		return [Equal::class, Greater::class, GreaterOrEqual::class, Smaller::class, SmallerOrEqual::class, BooleanAnd::class, Concat::class, ArrayDimFetch::class];
+	}
+
+	/**
+	 * refactor
+	 *
+	 * @param Node 	$node 		A node
+	 * @return    				Equal|Concat|ArrayDimFetch|void
+	 */
+	public function refactor(Node $node)
+	{
+		if ($node instanceof Node\Expr\ArrayDimFetch) {
+			if (!isset($node->dim)) {
+				return;
+			}
+			if ($this->isGlobalVar($node->dim)) {
+				$constName = $this->getConstName($node->dim);
+				if (empty($constName)) {
+					return;
+				}
+				$node->dim = new FuncCall(
+					new Name('getDolGlobalString'),
+					[new Arg($constName)]
+				);
+			}
+			return $node;
+		}
+		if ($node instanceof Concat) {
+			if ($this->isGlobalVar($node->left)) {
+				$constName = $this->getConstName($node->left);
+				if (empty($constName)) {
+					return;
+				}
+				$leftConcat = new FuncCall(
+					new Name('getDolGlobalString'),
+					[new Arg($constName)]
+				);
+				$rightConcat = $node->right;
+			}
+			if ($this->isGlobalVar($node->right)) {
+				$constName = $this->getConstName($node->right);
+				if (empty($constName)) {
+					return;
+				}
+				$rightConcat = new FuncCall(
+					new Name('getDolGlobalString'),
+					[new Arg($constName)]
+				);
+				$leftConcat = $node->left;
+			}
+			if (!isset($leftConcat, $rightConcat)) {
+				return;
+			}
+			return new Concat($leftConcat, $rightConcat);
+		}
+		if ($node instanceof BooleanAnd) {
+			$nodes = $this->resolveTwoNodeMatch($node);
+			if (!isset($nodes)) {
+				return;
+			}
+
+			/** @var Equal $node */
+			$node = $nodes->getFirstExpr();
+		}
+
+		$typeofcomparison = '';
+		if ($node instanceof Equal) {
+			$typeofcomparison = 'Equal';
+		}
+		if ($node instanceof Greater) {
+			$typeofcomparison = 'Greater';
+		}
+		if ($node instanceof GreaterOrEqual) {
+			$typeofcomparison = 'GreaterOrEqual';
+		}
+		if ($node instanceof Smaller) {
+			$typeofcomparison = 'Smaller';
+		}
+		if ($node instanceof SmallerOrEqual) {
+			$typeofcomparison = 'SmallerOrEqual';
+		}
+		if (empty($typeofcomparison)) {
+			return;
+		}
+
+		if (!$this->isGlobalVar($node->left)) {
+			return;
+		}
+
+		// Test the type after the comparison conf->global->xxx to know the name of function
+		switch ($node->right->getType()) {
+			case 'Scalar_LNumber':
+				$funcName = 'getDolGlobalInt';
+				break;
+			case 'Scalar_String':
+				$funcName = 'getDolGlobalString';
+				break;
+			default:
+				return;
+		}
+
+		$constName = $this->getConstName($node->left);
+		if (empty($constName)) {
+			return;
+		}
+
+		if ($typeofcomparison == 'Equal') {
+			return new Equal(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+				),
+				$node->right
+			);
+		}
+		if ($typeofcomparison == 'Greater') {
+			return new Greater(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+					),
+				$node->right
+				);
+		}
+		if ($typeofcomparison == 'GreaterOrEqual') {
+			return new GreaterOrEqual(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+					),
+				$node->right
+				);
+		}
+		if ($typeofcomparison == 'Smaller') {
+			return new Smaller(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+					),
+				$node->right
+				);
+		}
+		if ($typeofcomparison == 'SmallerOrEqual') {
+			return new SmallerOrEqual(
+				new FuncCall(
+					new Name($funcName),
+					[new Arg($constName)]
+					),
+				$node->right
+				);
+		}
+	}
+
+	/**
+	 * Get nodes with check empty
+	 *
+	 * @param BooleanAnd $booleanAnd A BooleandAnd
+	 * @return    TwoNodeMatch|null
+	 */
+	private function resolveTwoNodeMatch(BooleanAnd $booleanAnd): ?TwoNodeMatch
+	{
+		return $this->binaryOpManipulator->matchFirstAndSecondConditionNode(
+			$booleanAnd,
+			// $conf->global == $value
+			function (Node $node): bool {
+				if (!$node instanceof Equal) {
+					return \false;
+				}
+				return $this->isGlobalVar($node->left);
+			},
+			// !empty(...) || isset(...)
+			function (Node $node): bool {
+				if ($node instanceof BooleanNot && $node->expr instanceof Empty_) {
+					return $this->isGlobalVar($node->expr->expr);
+				}
+				if (!$node instanceof Isset_) {
+					return $this->isGlobalVar($node);
+				}
+				return \true;
+			}
+		);
+	}
+
+	/**
+	 * Check if node is a global access with format conf->global->XXX
+	 *
+	 * @param Node 	$node 	A node
+	 * @return bool			Return true if noe is conf->global->XXX
+	 */
+	private function isGlobalVar($node)
+	{
+		if (!$node instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($node->var, 'global')) {
+			return false;
+		}
+		$global = $node->var;
+		if (!$global instanceof PropertyFetch) {
+			return false;
+		}
+		if (!$this->isName($global->var, 'conf')) {
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * @param 	Node 		$node 	Node to be parsed
+	 * @return 	Node|void			Return the name of the constant
+	 */
+	private function getConstName($node)
+	{
+		if ($node instanceof PropertyFetch && $node->name instanceof Node\Expr) {
+			return $node->name;
+		}
+		$name = $this->getName($node);
+		if (empty($name)) {
+			return;
+		}
+		return new String_($name);
+	}
+}

+ 154 - 0
dev/tools/rector/src/Renaming/UserRightsToFunction.php

@@ -0,0 +1,154 @@
+<?php
+
+namespace Dolibarr\Rector\Renaming;
+
+
+use PhpParser\Node;
+use PhpParser\Node\Arg;
+use PhpParser\Node\Scalar\String_;
+use PhpParser\NodeTraverser;
+use Rector\Core\PhpParser\Node\NodeFactory;
+use Rector\Core\Rector\AbstractRector;
+use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
+use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
+
+/**
+ * Class to refactor User rights
+ */
+class UserRightsToFunction extends AbstractRector
+{
+	/**
+	 * @param \Rector\Core\PhpParser\Node\NodeFactory $nodeFactory node factory
+	 */
+	public function __construct(NodeFactory $nodeFactory)
+	{
+		$this->nodeFactory = $nodeFactory;
+	}
+
+	/**
+	 * @throws \Symplify\RuleDocGenerator\Exception\PoorDocumentationException
+	 * @return RuleDefinition
+	 */
+	public function getRuleDefinition(): RuleDefinition
+	{
+		return new RuleDefinition(
+			'Change \$user->rights->module->permission to \$user->hasRight(\'module\', \'permission\')',
+			[new CodeSample(
+				'$user->rights->module->permission',
+				'$user->hasRight(\'module\', \'permission\')'
+			)]);
+	}
+
+	/**
+	 * Return a node type from https://github.com/rectorphp/php-parser-nodes-docs/
+	 *
+	 * @return string[]
+	 */
+	public function getNodeTypes(): array
+	{
+		return [
+			Node\Expr\Assign::class,
+			Node\Expr\PropertyFetch::class,
+			Node\Expr\BooleanNot::class,
+			Node\Expr\Empty_::class,
+			Node\Expr\Isset_::class,
+			Node\Stmt\ClassMethod::class
+		];
+	}
+
+	/**
+	 * @param \PhpParser\Node $node node to be changed
+	 * @return \PhpParser\Node|\PhpParser\Node[]|\PhpParser\Node\Expr\MethodCall|void|null| int
+	 */
+	public function refactor(Node $node)
+	{
+
+		if ($node instanceof Node\Stmt\ClassMethod) {
+			$excludeMethods = ['getrights', 'hasRight'];
+			/** @var \PHPStan\Analyser\MutatingScope $scope */
+			$scope = $node->getAttribute('scope');
+			$class = $scope->getClassReflection();
+			$classes = ['UserGroup', 'User'];
+			if (isset($class) && in_array($class->getName(), $classes)) {
+				if (in_array($this->getName($node), $excludeMethods)) {
+					return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
+				}
+			}
+		}
+		if ($node instanceof Node\Expr\Assign) {
+			return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
+		}
+		$isInverse = false;
+		if ($node instanceof Node\Expr\BooleanNot) {
+			if (!$node->expr instanceof Node\Expr\Empty_) {
+				return null;
+			}
+			$node = $node->expr->expr;
+		}
+		if ($node instanceof Node\Expr\Empty_) {
+			$node = $node->expr;
+			$isInverse = true;
+		}
+		if ($node instanceof Node\Expr\Isset_) {
+			// Take first arg for isset (No code found with multiple isset).
+			$node = $node->vars[0];
+		}
+		if (!$node instanceof Node\Expr\PropertyFetch) {
+			return;
+		}
+		$data = $this->getRights($node);
+		if (!isset($data)) {
+			return;
+		}
+		$args = [new Arg($data['module']), new Arg($data['perm1'])];
+		if (!empty($data['perm2'])) {
+			$args[] = new Arg($data['perm2']);
+		}
+		$method = $this->nodeFactory->createMethodCall($data['user'], 'hasRight', $args);
+		if ($isInverse) {
+			return new Node\Expr\BooleanNot($method);
+		}
+		return $method;
+	}
+
+	/**
+	 * @param \PhpParser\Node\Expr\PropertyFetch $node node
+	 * @return array|null
+	 */
+	private function getRights(Node\Expr\PropertyFetch $node)
+	{
+		$perm2 = '';
+		if (!$node->var instanceof Node\Expr\PropertyFetch) {
+			return null;
+		}
+		// Add a test to avoid rector error on html.formsetup.class.php
+		if (! $node->name instanceof Node\Expr\Variable && is_null($this->getName($node))) {
+			//var_dump($node);
+			return null;
+			//exit;
+		}
+		$perm1 = $node->name instanceof Node\Expr\Variable ? $node->name : new String_($this->getName($node));
+		$moduleNode = $node->var;
+		if (!$moduleNode instanceof Node\Expr\PropertyFetch) {
+			return null;
+		}
+		if (!$moduleNode->var instanceof Node\Expr\PropertyFetch) {
+			return null;
+		}
+		if (!$this->isName($moduleNode->var, 'rights')) {
+			$perm2 = $perm1;
+			$perm1 = $moduleNode->name instanceof Node\Expr\Variable ? $moduleNode->name : new String_($this->getName($moduleNode));
+			$moduleNode = $moduleNode->var;
+		}
+		$module = $moduleNode->name instanceof Node\Expr\Variable ? $moduleNode->name : new String_($this->getName($moduleNode));
+		$rights = $moduleNode->var;
+		if (!$this->isName($rights, 'rights') || !isset($perm1) || !isset($module)) {
+			return null;
+		}
+		if (!$rights->var instanceof Node\Expr\Variable) {
+			return null;
+		}
+		$user = $rights->var;
+		return compact('user', 'module', 'perm1', 'perm2');
+	}
+}