From 6db323ee2582a4be95be6dab4ac390641bb897b5 Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Fri, 23 Sep 2022 16:35:00 +0200
Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20add=20rector.php=20with=20first?=
 =?UTF-8?q?=20configuration=20try?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 composer.json           |  20 +++---
 grumphp.yml             |  17 ++++-
 rector.php              |  41 ++++++++++++
 src/Composer/Plugin.php |  70 +++++++++++---------
 src/RectorSettings.php  | 142 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 248 insertions(+), 42 deletions(-)
 create mode 100644 rector.php
 create mode 100644 src/RectorSettings.php

diff --git a/composer.json b/composer.json
index ab3d649..c72d4d1 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,8 @@
           "*": "dist"
         },
         "allow-plugins": {
-            "phpro/grumphp": true
+            "phpro/grumphp": true,
+            "phpstan/extension-installer": true
         }
     },
     "support": {
@@ -28,24 +29,27 @@
     },
     "autoload": {
         "psr-4": {
-            "PLUS\\": "src/"
+            "PLUS\\GrumPHPConfig\\": "src/"
         }
     },
     "require": {
         "php": "~7.4 || ~8.0 || ~8.1",
-        "composer-plugin-api": "^1.0.0 || ^2.0.0",
-        "phpro/grumphp": "^1.5.1",
+        "composer-plugin-api": "^2.0.0",
+        "phpro/grumphp": "^1.13",
         "pluswerk/grumphp-bom-task": "^7.0.0",
         "pluswerk/grumphp-xliff-task": "^5.0.0",
-        "squizlabs/php_codesniffer": "^3.6.2",
+        "squizlabs/php_codesniffer": "^3.7.1",
         "enlightn/security-checker": "^1.10.0",
         "php-parallel-lint/php-parallel-lint": "^1.3.2",
-        "phpstan/phpstan": "^1.4.7"
+        "phpstan/phpstan": "^1.8.6",
+        "phpstan/extension-installer": "^1.1",
+        "rector/rector": "^0.14.3",
+        "palpalani/grumphp-rector-task": "^0.8.6"
     },
     "extra": {
-        "class": "PLUS\\Composer\\Plugin"
+        "class": "PLUS\\GrumPHPConfig\\Composer\\Plugin"
     },
     "require-dev": {
-        "composer/composer": "^1.10.25 || ^2.2.7"
+        "composer/composer": "^2.4.2"
     }
 }
diff --git a/grumphp.yml b/grumphp.yml
index c5404e1..f58badc 100644
--- a/grumphp.yml
+++ b/grumphp.yml
@@ -1,11 +1,13 @@
 parameters:
-  convention.process_timeout: 60
+  convention.process_timeout: 240
   convention.security_checker_blocking: true
   convention.jsonlint_ignore_pattern: []
   convention.xmllint_ignore_pattern: []
   convention.yamllint_ignore_pattern: []
   convention.phpcslint_ignore_pattern: []
-  convention.xlifflint_ignore_pattern: ["#typo3conf/l10n/(.*)#"]
+  convention.xlifflint_ignore_pattern: []
+  convention.rector_ignore_pattern: []
+  convention.rector_config: 'rector.php'
   convention.phpstan_level: max
 grumphp:
   stop_on_failure: false
@@ -29,7 +31,7 @@ grumphp:
       ignore_patterns: "%convention.jsonlint_ignore_pattern%"
     phpcs:
       standard: "PSR12"
-      warning_severity: 900000
+      warning_severity: 0
       tab_width: 4
       ignore_patterns: "%convention.phpcslint_ignore_pattern%"
     phpstan:
@@ -49,6 +51,15 @@ grumphp:
     plus_bom_fixer:
       metadata:
         priority: 1
+    rector:
+      whitelist_patterns: [ ]
+      config: "%convention.rector_config%"
+      triggered_by: [ 'php' ]
+      clear-cache: false
+      ignore_patterns: "%convention.rector_ignore_pattern%"
+      no-progress-bar: false
+      files_on_pre_commit: false
   extensions:
     - PLUS\GrumPHPBomTask\ExtensionLoader
     - PLUS\GrumPHPXliffTask\ExtensionLoader
+    - palPalani\GrumPhpRectorTask\ExtensionLoader
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..09e6f28
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+use PLUS\GrumPHPConfig\RectorSettings;
+use Rector\Config\RectorConfig;
+
+return static function (RectorConfig $rectorConfig): void {
+    $rectorConfig->parallel(240);
+    $rectorConfig->importNames();
+    $rectorConfig->importShortClasses();
+
+    $rectorConfig->paths(
+        [
+            //remove that you don't need
+            __DIR__ . '/src',
+            #__DIR__ . '/extensions',
+        ]
+    );
+
+    // define sets of rules
+    $rectorConfig->sets(
+        [
+            ...RectorSettings::sets(),
+        ]
+    );
+
+    // remove some rules
+    // ignore some files
+    $rectorConfig->skip(
+        [
+            ...RectorSettings::skip(),
+
+            /**
+             * do not touch these files
+             */
+            __DIR__ . '/src/Example',
+            __DIR__ . '/src/Example.php',
+        ]
+    );
+};
diff --git a/src/Composer/Plugin.php b/src/Composer/Plugin.php
index 95c8f28..3740c1a 100644
--- a/src/Composer/Plugin.php
+++ b/src/Composer/Plugin.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace PLUS\Composer;
+namespace PLUS\GrumPHPConfig\Composer;
 
 use Composer\Composer;
 use Composer\DependencyResolver\Operation\InstallOperation;
@@ -17,38 +17,25 @@
 
 class Plugin implements PluginInterface, EventSubscriberInterface
 {
+    /** @var string */
     private const PACKAGE_NAME = 'pluswerk/grumphp-config';
+
+    /** @var string */
     private const DEFAULT_CONFIG_PATH = 'vendor/' . self::PACKAGE_NAME . '/grumphp.yml';
 
-    /**
-     * @var Composer
-     */
     protected Composer $composer;
 
-    /**
-     * @var IOInterface
-     */
-    protected IOInterface $consoleIo;
+    protected IOInterface $io;
 
-    /**
-     * @var array<string, mixed>
-     */
-    protected array $extra;
+    /** @var array<string, mixed> */
+    protected array $extra = [];
 
-    /**
-     * @var bool
-     */
     protected bool $shouldSetConfigPath = false;
 
-    /**
-     * @param \Composer\Composer $composer
-     * @param \Composer\IO\IOInterface $consoleIo
-     * @retrun void
-     */
-    public function activate(Composer $composer, IOInterface $consoleIo): void
+    public function activate(Composer $composer, IOInterface $io): void
     {
         $this->composer = $composer;
-        $this->consoleIo = $consoleIo;
+        $this->io = $io;
         $this->extra = $this->composer->getPackage()->getExtra();
     }
 
@@ -64,6 +51,8 @@ public static function getSubscribedEvents(): array
 
             ScriptEvents::POST_INSTALL_CMD => 'runScheduledTasks',
             ScriptEvents::POST_UPDATE_CMD => 'runScheduledTasks',
+
+            ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
         ];
     }
 
@@ -106,6 +95,7 @@ public function setConfigPath(): void
             $this->message('not setting config path, extra.' . self::PACKAGE_NAME . '.auto-setting is false', 'yellow');
             return;
         }
+
         $this->setExtra(self::PACKAGE_NAME . '.auto-setting', true);
         if ($this->getExtra('grumphp.config-default-path') !== self::DEFAULT_CONFIG_PATH) {
             $this->setExtra('grumphp.config-default-path', self::DEFAULT_CONFIG_PATH);
@@ -119,6 +109,7 @@ public function removeConfigPath(): void
             $this->message('not removing config path, extra.' . self::PACKAGE_NAME . '.auto-setting is false', 'yellow');
             return;
         }
+
         unset($this->extra[self::PACKAGE_NAME]);
         $this->removeExtra(self::PACKAGE_NAME);
 
@@ -128,6 +119,7 @@ public function removeConfigPath(): void
         } elseif ($this->getExtra()) {
             $key = 'grumphp';
         }
+
         $this->removeExtra($key);
         $this->message('auto removed config path and ' . self::PACKAGE_NAME . ' settings', 'green');
     }
@@ -143,26 +135,24 @@ public function getExtra(string $name = null)
         if ($name === null) {
             return $this->extra;
         }
+
         $arr = $this->extra;
         $bits = explode('.', $name);
         $last = array_pop($bits);
         foreach ($bits as $bit) {
-            if (!isset($arr[$bit])) {
-                $arr[$bit] = [];
-            }
+            $arr[$bit] ??= [];
+
             if (!is_array($arr[$bit])) {
                 return null;
             }
+
             $arr = &$arr[$bit];
         }
-        if (isset($arr[$last])) {
-            return $arr[$last];
-        }
-        return null;
+
+        return $arr[$last] ?? null;
     }
 
     /**
-     * @param string $name
      * @param string|bool $value
      */
     public function setExtra(string $name, $value): void
@@ -177,6 +167,7 @@ public function removeExtra(string $name = null): void
         if ($name !== null) {
             $key .= '.' . $name;
         }
+
         $configSource = $this->composer->getConfig()->getConfigSource();
         $configSource->removeProperty($key);
     }
@@ -189,7 +180,8 @@ public function message(string $message, string $color = null): void
             $colorStart = '<fg=' . $color . '>';
             $colorEnd = '</fg=' . $color . '>';
         }
-        $this->consoleIo->write(self::PACKAGE_NAME . ': ' . $colorStart . $message . $colorEnd);
+
+        $this->io->write(self::PACKAGE_NAME . ': ' . $colorStart . $message . $colorEnd);
     }
 
     /**
@@ -205,4 +197,20 @@ public function deactivate(Composer $composer, IOInterface $io): void
     public function uninstall(Composer $composer, IOInterface $io): void
     {
     }
+
+
+    // Rector config copy:
+
+    public function postAutoloadDump(): void
+    {
+        $this->createRectorConfig();
+    }
+
+    private function createRectorConfig(): void
+    {
+        if (!file_exists(getcwd() . '/rector.php')) {
+            copy(dirname(__DIR__, 2) . '/rector.php', getcwd() . '/rector.php');
+            $this->message('rector.php file created', 'yellow');
+        }
+    }
 }
diff --git a/src/RectorSettings.php b/src/RectorSettings.php
new file mode 100644
index 0000000..27f4241
--- /dev/null
+++ b/src/RectorSettings.php
@@ -0,0 +1,142 @@
+<?php
+
+declare(strict_types=1);
+
+namespace PLUS\GrumPHPConfig;
+
+use Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector;
+use Rector\CodeQuality\Rector\If_\SimplifyIfElseToTernaryRector;
+use Rector\CodeQuality\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector;
+use Rector\CodingStyle\Rector\ClassMethod\UnSpreadOperatorRector;
+use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector;
+use Rector\CodingStyle\Rector\If_\NullableCompareToNullRector;
+use Rector\CodingStyle\Rector\PostInc\PostIncDecToPreIncDecRector;
+use Rector\CodingStyle\Rector\Switch_\BinarySwitchToIfElseRector;
+use Rector\Php70\Rector\Assign\ListSwapArrayOrderRector;
+use Rector\Php74\Rector\LNumber\AddLiteralSeparatorToNumberRector;
+use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
+use Rector\Privatization\Rector\Class_\ChangeReadOnlyVariableWithDefaultValueToConstantRector;
+use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenRector;
+use Rector\Set\ValueObject\LevelSetList;
+use Rector\Set\ValueObject\SetList;
+
+class RectorSettings
+{
+    /**
+     * @return array<int,string>
+     */
+    public static function sets(): array
+    {
+        $phpfile = constant(LevelSetList::class . '::UP_TO_PHP_' . PHP_MAJOR_VERSION . PHP_MINOR_VERSION);
+        assert(is_string($phpfile));
+
+        return [
+            // SetList::ACTION_INJECTION_TO_CONSTRUCTOR_INJECTION, // NO
+            SetList::CODE_QUALITY, // YES
+            SetList::CODING_STYLE, // YES
+            SetList::DEAD_CODE, // YES
+            //SetList::GMAGICK_TO_IMAGICK, // NO
+            //SetList::MONOLOG_20, // no usage
+            //SetList::MYSQL_TO_MYSQLI, // no usage
+            //SetList::NAMING, //NO is not good
+            $phpfile,
+            //SetList::PHP_52, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_53, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_54, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_55, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_56, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_70, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_71, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_72, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_73, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_74, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_80, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_81, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            //SetList::PHP_82, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
+            SetList::PRIVATIZATION, // some things may be bad
+            SetList::PSR_4, // dose nothing?
+            SetList::TYPE_DECLARATION, // YES
+            SetList::TYPE_DECLARATION_STRICT, // YES please
+            SetList::EARLY_RETURN,  //YES
+            //SetList::RECTOR_CONFIG, // NO
+        ];
+    }
+
+    /**
+     * @return array<int, string>
+     */
+    public static function skip(): array
+    {
+        return [
+            /**
+             * FROM: if($object) {
+             * TO:   if($object !== null) {
+             */
+            NullableCompareToNullRector::class,
+            /**
+             * FROM: $i++
+             * TO:   ++$i
+             */
+            PostIncDecToPreIncDecRector::class,
+            /**
+             * FROM: if(count($array)) {
+             * TO:   if($array !== []) {
+             */
+            CountArrayToEmptyArrayComparisonRector::class,
+            /**
+             * FROM: switch($x) {
+             * TO:   if($X === '...') {
+             */
+            BinarySwitchToIfElseRector::class,
+            /**
+             * FROM: ->select('a', 'b')
+             * TO:   ->select(['a', 'b'])
+             */
+            UnSpreadOperatorRector::class,
+            /**
+             * FROM: $domain = 'https://efsrgtdhj';
+             * TO:   self::DOMAIN
+             */
+            ChangeReadOnlyVariableWithDefaultValueToConstantRector::class,
+            /**
+             * FROM: class XYZ {
+             * TO:   final class XYZ {
+             */
+            FinalizeClassesWithoutChildrenRector::class,
+            /**
+             * DOCS: be careful, run this just once, since it can keep swapping order back and forth
+             * => we don't do it once!
+             */
+            ListSwapArrayOrderRector::class,
+            /**
+             * FROM: 1305630314
+             * TO:   1_305_630_314
+             */
+            AddLiteralSeparatorToNumberRector::class,
+            /**
+             * Maybe to a later date?
+             *
+             * FROM: public string $username = '';
+             * TO:   __construct(public string $username = '')
+             */
+            ClassPropertyAssignToConstructorPromotionRector::class,
+            /**
+             * Maybe to a later date?
+             *
+             * FROM: if($x) {
+             * TO:   $x ? $abcde + $xyz : $trsthjzuj - $gesrtdnzmf
+             */
+            SimplifyIfElseToTernaryRector::class,
+            /**
+             * FROM: if ($timeInMinutes % 60) {
+             * TO:   if ($timeInMinutes % 60 !== 0) {
+             */
+            ExplicitBoolCompareRector::class,
+            /**
+             * FROM: isset($this->x);
+             * TO:   property_exists($this, 'x') && $this->x !== null;
+             */
+            IssetOnPropertyObjectToPropertyExistsRector::class,
+        ];
+    }
+}

From 14a387b73422de2a6bc9dfc6b45059a944207857 Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Sat, 8 Oct 2022 12:27:48 +0200
Subject: [PATCH 2/8] Update rector.php

---
 rector.php | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/rector.php b/rector.php
index 09e6f28..06c03b0 100644
--- a/rector.php
+++ b/rector.php
@@ -14,7 +14,8 @@
         [
             //remove that you don't need
             __DIR__ . '/src',
-            #__DIR__ . '/extensions',
+            //__DIR__ . '/extensions',
+            //__DIR__ . '/Classes',
         ]
     );
 
@@ -32,10 +33,10 @@
             ...RectorSettings::skip(),
 
             /**
-             * do not touch these files
+             * rector should not touch these files
              */
-            __DIR__ . '/src/Example',
-            __DIR__ . '/src/Example.php',
+            //__DIR__ . '/src/Example',
+            //__DIR__ . '/src/Example.php',
         ]
     );
 };

From ab3043c489bca7db86b76b17a5d8e5b534674acd Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Sun, 9 Oct 2022 11:22:20 +0200
Subject: [PATCH 3/8] =?UTF-8?q?=E2=9C=A8=20do=20not=20change=20composer.js?=
 =?UTF-8?q?on=20for=20grumphp.yml=20config=20path?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .github/workflows/run-grumphp-test.yml |   3 +-
 composer.json                          | 102 +++++------
 grumphp.yml                            |   4 +-
 phpstan-baseline.neon                  |  51 ++++++
 phpstan.neon                           |   8 +-
 rector.php                             |  11 +-
 src/Composer/Plugin.php                | 231 ++++++++++---------------
 src/RectorSettings.php                 |   3 +-
 src/VersionUtility.php                 |  43 +++++
 stubs/ConfigSourceInterface.stub       |  15 --
 10 files changed, 253 insertions(+), 218 deletions(-)
 create mode 100644 phpstan-baseline.neon
 create mode 100644 src/VersionUtility.php
 delete mode 100644 stubs/ConfigSourceInterface.stub

diff --git a/.github/workflows/run-grumphp-test.yml b/.github/workflows/run-grumphp-test.yml
index 59bdd24..7472e64 100644
--- a/.github/workflows/run-grumphp-test.yml
+++ b/.github/workflows/run-grumphp-test.yml
@@ -9,14 +9,13 @@ jobs:
           - '7.4'
           - '8.0'
           - '8.1'
+          - '8.2-rc'
         composer:
           - install --no-progress --no-scripts -n
-          - require phpro/grumphp:* --no-progress --no-scripts -n
           - update --no-progress --no-scripts -n --prefer-lowest
     container:
       image: kanti/buildy:${{ matrix.php }}
     steps:
       - uses: actions/checkout@v2
-      - run: jq '.config.platform.php = "${{ matrix.php }}"' composer.json > composer.json_tmp && mv composer.json_tmp composer.json
       - run: composer ${{ matrix.composer }}
       - run: ./vendor/bin/grumphp run
diff --git a/composer.json b/composer.json
index c72d4d1..2fc4106 100644
--- a/composer.json
+++ b/composer.json
@@ -1,55 +1,55 @@
 {
-    "name": "pluswerk/grumphp-config",
-    "description": "GrumPHP config for php projects(mainly TYPO3)",
-    "type": "composer-plugin",
-    "license": "LGPL-3.0-or-later",
-    "authors": [
-        {
-            "name": "Matthias Vogel",
-            "email": "matthias.vogel@pluswerk.ag",
-            "homepage": "http://pluswerk.ag"
-        }
-    ],
-    "config": {
-        "platform": {
-            "php": "7.4"
-        },
-        "preferred-install": {
-          "pluswerk/grumphp-bom-task": "source",
-          "pluswerk/grumphp-xliff-task": "source",
-          "*": "dist"
-        },
-        "allow-plugins": {
-            "phpro/grumphp": true,
-            "phpstan/extension-installer": true
-        }
-    },
-    "support": {
-        "issues": "https://github.com/pluswerk/grumphp-config/issues"
-    },
-    "autoload": {
-        "psr-4": {
-            "PLUS\\GrumPHPConfig\\": "src/"
-        }
-    },
-    "require": {
-        "php": "~7.4 || ~8.0 || ~8.1",
-        "composer-plugin-api": "^2.0.0",
-        "phpro/grumphp": "^1.13",
-        "pluswerk/grumphp-bom-task": "^7.0.0",
-        "pluswerk/grumphp-xliff-task": "^5.0.0",
-        "squizlabs/php_codesniffer": "^3.7.1",
-        "enlightn/security-checker": "^1.10.0",
-        "php-parallel-lint/php-parallel-lint": "^1.3.2",
-        "phpstan/phpstan": "^1.8.6",
-        "phpstan/extension-installer": "^1.1",
-        "rector/rector": "^0.14.3",
-        "palpalani/grumphp-rector-task": "^0.8.6"
-    },
-    "extra": {
-        "class": "PLUS\\GrumPHPConfig\\Composer\\Plugin"
+  "name" : "pluswerk/grumphp-config",
+  "description" : "GrumPHP config for php projects(mainly TYPO3)",
+  "type" : "composer-plugin",
+  "license" : "LGPL-3.0-or-later",
+  "authors" : [
+    {
+      "name" : "Matthias Vogel",
+      "email" : "matthias.vogel@pluswerk.ag",
+      "homepage" : "https://pluswerk.ag"
+    }
+  ],
+  "config" : {
+    "preferred-install" : {
+      "pluswerk/grumphp-bom-task" : "source",
+      "pluswerk/grumphp-xliff-task" : "source",
+      "*" : "dist"
     },
-    "require-dev": {
-        "composer/composer": "^2.4.2"
+    "allow-plugins" : {
+      "phpro/grumphp" : true,
+      "phpstan/extension-installer" : true
+    }
+  },
+  "support" : {
+    "issues" : "https://github.com/pluswerk/grumphp-config/issues"
+  },
+  "autoload" : {
+    "psr-4" : {
+      "PLUS\\GrumPHPConfig\\" : "src/"
     }
+  },
+  "require" : {
+    "php" : "~7.4 || ~8.0 || ~8.1 || ~8.2",
+    "composer-plugin-api" : "^2.1.0",
+    "phpro/grumphp" : "^1.13",
+    "pluswerk/grumphp-bom-task" : "^7.0.0",
+    "pluswerk/grumphp-xliff-task" : "^5.0.0",
+    "squizlabs/php_codesniffer" : "^3.7.1",
+    "enlightn/security-checker" : "^1.10.0",
+    "php-parallel-lint/php-parallel-lint" : "^1.3.2",
+    "phpstan/phpstan" : "^1.8.8",
+    "phpstan/extension-installer" : "^1.1",
+    "rector/rector" : "^0.14.5",
+    "palpalani/grumphp-rector-task" : "^0.8.6",
+    "composer/semver" : "^3.3.0",
+    "symfony/yaml" : "^5.4.0 || ^6.0"
+  },
+  "extra" : {
+    "class" : "PLUS\\GrumPHPConfig\\Composer\\Plugin"
+  },
+  "require-dev" : {
+    "roave/security-advisories" : "dev-latest",
+    "composer/composer" : "^2.4.2"
+  }
 }
diff --git a/grumphp.yml b/grumphp.yml
index f58badc..c4ed91a 100644
--- a/grumphp.yml
+++ b/grumphp.yml
@@ -8,7 +8,9 @@ parameters:
   convention.xlifflint_ignore_pattern: []
   convention.rector_ignore_pattern: []
   convention.rector_config: 'rector.php'
+  convention.rector_clear-cache: false
   convention.phpstan_level: max
+
 grumphp:
   stop_on_failure: false
   hide_circumvention_tip: true
@@ -55,7 +57,7 @@ grumphp:
       whitelist_patterns: [ ]
       config: "%convention.rector_config%"
       triggered_by: [ 'php' ]
-      clear-cache: false
+      clear-cache: "%convention.rector_clear-cache%"
       ignore_patterns: "%convention.rector_ignore_pattern%"
       no-progress-bar: false
       files_on_pre_commit: false
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000..d396b7f
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,51 @@
+parameters:
+	ignoreErrors:
+		-
+			message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
+			count: 1
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot access offset 'config\\-default\\-path' on mixed\\.$#"
+			count: 1
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot access offset 'imports' on mixed\\.$#"
+			count: 1
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot access offset 'parameters' on mixed\\.$#"
+			count: 2
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot access offset 'resource' on mixed\\.$#"
+			count: 1
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot access offset 0 on mixed\\.$#"
+			count: 1
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot access offset mixed on mixed\\.$#"
+			count: 2
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot cast mixed to string\\.$#"
+			count: 1
+			path: src/Composer/Plugin.php
+
+		-
+			message: "#^Cannot access offset 'php' on mixed\\.$#"
+			count: 1
+			path: src/VersionUtility.php
+
+		-
+			message: "#^Method PLUS\\\\GrumPHPConfig\\\\VersionUtility\\:\\:getRootComposerJsonData\\(\\) should return array but returns mixed\\.$#"
+			count: 1
+			path: src/VersionUtility.php
diff --git a/phpstan.neon b/phpstan.neon
index 8682e43..4bbcb1f 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,3 +1,7 @@
+includes:
+  - phpstan-baseline.neon
+
 parameters:
-	stubFiles:
-		- stubs/ConfigSourceInterface.stub
+  level: max
+  paths:
+    - src
diff --git a/rector.php b/rector.php
index 06c03b0..3ddaa30 100644
--- a/rector.php
+++ b/rector.php
@@ -11,12 +11,11 @@
     $rectorConfig->importShortClasses();
 
     $rectorConfig->paths(
-        [
-            //remove that you don't need
-            __DIR__ . '/src',
-            //__DIR__ . '/extensions',
-            //__DIR__ . '/Classes',
-        ]
+        array_filter([
+            is_dir(__DIR__ . '/src') ? __DIR__ . '/src' : null,
+            is_dir(__DIR__ . '/extensions') ? __DIR__ . '/extensions' : null,
+            is_dir(__DIR__ . '/Classes') ? __DIR__ . '/Classes' : null,
+        ])
     );
 
     // define sets of rules
diff --git a/src/Composer/Plugin.php b/src/Composer/Plugin.php
index 3740c1a..8fb0d20 100644
--- a/src/Composer/Plugin.php
+++ b/src/Composer/Plugin.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace PLUS\GrumPHPConfig\Composer;
 
 use Composer\Composer;
@@ -11,32 +13,36 @@
 use Composer\Installer\PackageEvents;
 use Composer\IO\IOInterface;
 use Composer\Plugin\PluginInterface;
-use Composer\Script\Event;
 use Composer\Script\ScriptEvents;
-use GrumPHP\Event\TaskEvents;
+use GrumPHP\Configuration\Configuration;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Routing\Loader\YamlFileLoader;
+use Symfony\Component\Yaml\Yaml;
 
-class Plugin implements PluginInterface, EventSubscriberInterface
+final class Plugin implements PluginInterface, EventSubscriberInterface
 {
-    /** @var string */
-    private const PACKAGE_NAME = 'pluswerk/grumphp-config';
-
-    /** @var string */
-    private const DEFAULT_CONFIG_PATH = 'vendor/' . self::PACKAGE_NAME . '/grumphp.yml';
-
-    protected Composer $composer;
+    private Composer $composer;
 
-    protected IOInterface $io;
-
-    /** @var array<string, mixed> */
-    protected array $extra = [];
-
-    protected bool $shouldSetConfigPath = false;
+    private IOInterface $io;
 
     public function activate(Composer $composer, IOInterface $io): void
     {
         $this->composer = $composer;
         $this->io = $io;
-        $this->extra = $this->composer->getPackage()->getExtra();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deactivate(Composer $composer, IOInterface $io): void
+    {
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function uninstall(Composer $composer, IOInterface $io): void
+    {
     }
 
     /**
@@ -45,134 +51,109 @@ public function activate(Composer $composer, IOInterface $io): void
     public static function getSubscribedEvents(): array
     {
         return [
-            PackageEvents::POST_PACKAGE_UPDATE => 'postPackageUpdate',
-            PackageEvents::POST_PACKAGE_INSTALL => 'postPackageInstall',
-            PackageEvents::PRE_PACKAGE_UNINSTALL => 'prePackageUninstall',
+            PackageEvents::POST_PACKAGE_UPDATE => 'heavyProcessing',
+            PackageEvents::POST_PACKAGE_INSTALL => 'heavyProcessing',
 
-            ScriptEvents::POST_INSTALL_CMD => 'runScheduledTasks',
-            ScriptEvents::POST_UPDATE_CMD => 'runScheduledTasks',
+            ScriptEvents::POST_INSTALL_CMD => 'heavyProcessing',
+            ScriptEvents::POST_UPDATE_CMD => 'heavyProcessing',
 
-            ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
+            ScriptEvents::POST_AUTOLOAD_DUMP => 'simpleProcessing',
         ];
     }
 
-    public function postPackageUpdate(PackageEvent $event): void
+    public function heavyProcessing(): void
     {
-        $operation = $event->getOperation();
-        if ($operation instanceof UpdateOperation && $operation->getTargetPackage()->getName() === self::PACKAGE_NAME) {
-            $this->shouldSetConfigPath = true;
-        }
+        $this->removeOldConfigPath();
+        $this->createGrumphpConfig();
+
+        $this->simpleProcessing();
     }
 
-    public function postPackageInstall(PackageEvent $event): void
+    public function simpleProcessing(): void
     {
-        $operation = $event->getOperation();
-        if ($operation instanceof InstallOperation && $operation->getPackage()->getName() === self::PACKAGE_NAME) {
-            $this->shouldSetConfigPath = true;
-        }
+        $this->createRectorConfig();
     }
 
-    public function prePackageUninstall(PackageEvent $event): void
+    private function removeOldConfigPath(): void
     {
-        $operation = $event->getOperation();
-        if ($operation instanceof UninstallOperation && $operation->getPackage()->getName() === self::PACKAGE_NAME) {
-            $this->removeConfigPath();
+        $rootPackage = $this->composer->getPackage();
+        $extra = $rootPackage->getExtra();
+        $configSource = $this->composer->getConfig()->getConfigSource();
+
+        $configDefaultPath = $extra['grumphp']['config-default-path'] ?? '';
+        if (in_array($configDefaultPath, ['grumphp.yml', 'vendor/pluswerk/grumphp-config/grumphp.yml'])) {
+            unset($extra['grumphp']['config-default-path']);
+            $configSource->removeProperty('extra.grumphp.config-default-path');
+            $this->message('removed extra.grumphp.config-default-path', 'yellow');
+            if (empty($extra['grumphp'])) {
+                unset($extra['grumphp']);
+                $configSource->removeProperty('extra.grumphp');
+                $this->message('removed extra.grumphp', 'yellow');
+            }
         }
-    }
 
-    public function runScheduledTasks(): void
-    {
-        if ($this->shouldSetConfigPath) {
-            $this->setConfigPath();
+        if (isset($extra['pluswerk/grumphp-config'])) {
+            unset($extra['pluswerk/grumphp-config']);
+            $configSource->removeProperty('extra.pluswerk/grumphp-config');
+            $this->message('removed extra.pluswerk/grumphp-config', 'yellow');
         }
-    }
 
-    //ACTIONS:
+        $rootPackage->setExtra($extra);
+    }
 
-    public function setConfigPath(): void
+    private function createRectorConfig(): void
     {
-        if ($this->getExtra(self::PACKAGE_NAME . '.auto-setting') === false) {
-            $this->message('not setting config path, extra.' . self::PACKAGE_NAME . '.auto-setting is false', 'yellow');
-            return;
-        }
-
-        $this->setExtra(self::PACKAGE_NAME . '.auto-setting', true);
-        if ($this->getExtra('grumphp.config-default-path') !== self::DEFAULT_CONFIG_PATH) {
-            $this->setExtra('grumphp.config-default-path', self::DEFAULT_CONFIG_PATH);
-            $this->message('auto setting grumphp.config-default-path', 'green');
+        if (!file_exists(getcwd() . '/rector.php')) {
+            copy(dirname(__DIR__, 2) . '/rector.php', getcwd() . '/rector.php');
+            $this->message('rector.php file created', 'yellow');
         }
     }
 
-    public function removeConfigPath(): void
+    private function createGrumphpConfig(): void
     {
-        if ($this->getExtra(self::PACKAGE_NAME . '.auto-setting') === false) {
-            $this->message('not removing config path, extra.' . self::PACKAGE_NAME . '.auto-setting is false', 'yellow');
-            return;
+        $grumphpPath = getcwd() . '/grumphp.yml';
+        $grumphpTemplatePath = dirname(__DIR__, 2) . '/grumphp.yml';
+        if (!file_exists($grumphpPath)) {
+            $defaultImport = [
+                'imports' => [
+                    ['resource' => 'vendor/pluswerk/grumphp-config/grumphp.yml'],
+                ],
+            ];
+            file_put_contents($grumphpPath, Yaml::dump($defaultImport));
+            $this->message('grumphp.yml file created', 'yellow');
         }
 
-        unset($this->extra[self::PACKAGE_NAME]);
-        $this->removeExtra(self::PACKAGE_NAME);
+        $data = Yaml::parseFile($grumphpPath);
 
-        $key = null;
-        if ($this->getExtra('grumphp')) {
-            $key = 'grumphp.config-default-path';
-        } elseif ($this->getExtra()) {
-            $key = 'grumphp';
+        if (($data['imports'][0]['resource'] ?? '') !== 'vendor/pluswerk/grumphp-config/grumphp.yml') {
+            return;
         }
 
-        $this->removeExtra($key);
-        $this->message('auto removed config path and ' . self::PACKAGE_NAME . ' settings', 'green');
-    }
+        $changed = false;
 
-    // HELPER:
-
-    /**
-     * @param string|null $name
-     * @return mixed
-     */
-    public function getExtra(string $name = null)
-    {
-        if ($name === null) {
-            return $this->extra;
-        }
-
-        $arr = $this->extra;
-        $bits = explode('.', $name);
-        $last = array_pop($bits);
-        foreach ($bits as $bit) {
-            $arr[$bit] ??= [];
+        $templateData = Yaml::parseFile($grumphpTemplatePath);
+        foreach ($templateData['parameters'] ?? [] as $key => $value) {
+            if (!str_starts_with((string)$key, 'convention.')) {
+                continue;
+            }
 
-            if (!is_array($arr[$bit])) {
-                return null;
+            if (($data['parameters'][$key] ?? null) === $value) {
+                continue;
             }
 
-            $arr = &$arr[$bit];
+            $data['parameters'][$key] = $value;
+            $changed = true;
         }
 
-        return $arr[$last] ?? null;
-    }
-
-    /**
-     * @param string|bool $value
-     */
-    public function setExtra(string $name, $value): void
-    {
-        $configSource = $this->composer->getConfig()->getConfigSource();
-        $configSource->addProperty('extra.' . $name, $value);
-    }
-
-    public function removeExtra(string $name = null): void
-    {
-        $key = 'extra';
-        if ($name !== null) {
-            $key .= '.' . $name;
+        if ($changed) {
+            file_put_contents($grumphpPath, Yaml::dump($data));
+            $this->message('added some default conventions to grumphp.yml', 'yellow');
         }
-
-        $configSource = $this->composer->getConfig()->getConfigSource();
-        $configSource->removeProperty($key);
     }
 
-    public function message(string $message, string $color = null): void
+    // HELPER:
+
+    private function message(string $message, string $color = null): void
     {
         $colorStart = '';
         $colorEnd = '';
@@ -181,36 +162,6 @@ public function message(string $message, string $color = null): void
             $colorEnd = '</fg=' . $color . '>';
         }
 
-        $this->io->write(self::PACKAGE_NAME . ': ' . $colorStart . $message . $colorEnd);
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function deactivate(Composer $composer, IOInterface $io): void
-    {
-    }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function uninstall(Composer $composer, IOInterface $io): void
-    {
-    }
-
-
-    // Rector config copy:
-
-    public function postAutoloadDump(): void
-    {
-        $this->createRectorConfig();
-    }
-
-    private function createRectorConfig(): void
-    {
-        if (!file_exists(getcwd() . '/rector.php')) {
-            copy(dirname(__DIR__, 2) . '/rector.php', getcwd() . '/rector.php');
-            $this->message('rector.php file created', 'yellow');
-        }
+        $this->io->write('pluswerk/grumphp-config' . ': ' . $colorStart . $message . $colorEnd);
     }
 }
diff --git a/src/RectorSettings.php b/src/RectorSettings.php
index 27f4241..cc87b11 100644
--- a/src/RectorSettings.php
+++ b/src/RectorSettings.php
@@ -27,7 +27,8 @@ class RectorSettings
      */
     public static function sets(): array
     {
-        $phpfile = constant(LevelSetList::class . '::UP_TO_PHP_' . PHP_MAJOR_VERSION . PHP_MINOR_VERSION);
+        $phpVersion = VersionUtility::getMinimalPhpVersion() ?? PHP_MAJOR_VERSION . PHP_MINOR_VERSION;
+        $phpfile = constant(LevelSetList::class . '::UP_TO_PHP_' . $phpVersion);
         assert(is_string($phpfile));
 
         return [
diff --git a/src/VersionUtility.php b/src/VersionUtility.php
new file mode 100644
index 0000000..6b7430e
--- /dev/null
+++ b/src/VersionUtility.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+namespace PLUS\GrumPHPConfig;
+
+use Composer\InstalledVersions;
+use Composer\Semver\VersionParser;
+
+use function json_decode;
+
+final class VersionUtility
+{
+    public static function getMinimalPhpVersion(): ?string
+    {
+        $phpVersionConstrain = self::getRootComposerJsonData()['require']['php'] ?? false;
+        if (!$phpVersionConstrain) {
+            return null;
+        }
+
+        if (!is_string($phpVersionConstrain)) {
+            return null;
+        }
+
+        $parser = new VersionParser();
+        $lowerPhpVersion = $parser->parseConstraints($phpVersionConstrain)->getLowerBound()->getVersion();
+        if (!preg_match('#(?<major>\d)\.(?<minor>\d)\..*#', $lowerPhpVersion, $matches)) {
+            return null;
+        }
+
+        return $matches['major'] . $matches['minor'];
+    }
+
+    /**
+     * @return array<mixed>
+     */
+    private static function getRootComposerJsonData(): array
+    {
+        $rootPackage = InstalledVersions::getRootPackage();
+        $contents = file_get_contents($rootPackage['install_path'] . 'composer.json');
+        return json_decode((string)$contents, true, 512, JSON_THROW_ON_ERROR) ?: [];
+    }
+}
diff --git a/stubs/ConfigSourceInterface.stub b/stubs/ConfigSourceInterface.stub
deleted file mode 100644
index ffae886..0000000
--- a/stubs/ConfigSourceInterface.stub
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-namespace Composer\Config;
-
-interface ConfigSourceInterface {
-    /**
-     * Add a property
-     *
-     * @param string $name  Name
-     * @param string|string[]|bool|float|int $value Value
-     *
-     * @return void
-     */
-    public function addProperty($name, $value);
-}

From 7dd9a780f20ad2adb80872e62f7d8cdde826526a Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Sun, 9 Oct 2022 17:21:52 +0200
Subject: [PATCH 4/8] =?UTF-8?q?=E2=9C=A8=20update=20phpstan=20errors=20and?=
 =?UTF-8?q?=20README.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md               | 40 ++---------------------------------
 phpstan-baseline.neon   | 35 ------------------------------
 phpstan.neon            |  1 +
 src/Composer/Plugin.php | 47 ++++++++++++++++++++++++++++++++++-------
 4 files changed, 42 insertions(+), 81 deletions(-)

diff --git a/README.md b/README.md
index 62cbcd4..8d4fc15 100644
--- a/README.md
+++ b/README.md
@@ -8,47 +8,11 @@
 composer require pluswerk/grumphp-config --dev
 ````
 
-pluswerk/grumphp-config will add the required ``extra.grumphp.config-default-path`` automatically to your ``composer.json``.
-
-If pluswerk/grumphp-config should not edit your composer.json then you must add this:
-````json
-{
-  "extra": {
-    "pluswerk/grumphp-config": {
-      "auto-setting": false
-    }
-  }
-}
-````
+pluswerk/grumphp-config will create `grumphp.yml`, `rector.php` and require some project specific resources if necessary 
 
 ### You want to override settings?:
 
-
-Make a new grumphp.yml config file. You can put it in the root folder.
-````yaml
-imports:
-  - { resource: vendor/pluswerk/grumphp-config/grumphp.yml }
-
-
-parameters:
-  convention.phpstan_level: 1
-  convention.xmllint_ignore_pattern:
-    - "typo3conf/ext/extension/Resources/Private/Templates/List.xml"
-````
-
-There you can override some convention:
-
-
-| Key                                 | Default                       |
-|-------------------------------------|-------------------------------|
-| convention.process_timeout          | 60                            |
-| convention.security_checker_blocking| true                          |
-| convention.jsonlint_ignore_pattern  | []                            |
-| convention.xmllint_ignore_pattern   | []                            |
-| convention.yamllint_ignore_pattern  | []                            |
-| convention.phpcslint_ignore_pattern | []                            |
-| convention.xlifflint_ignore_pattern | ["#typo3conf/l10n/(.*)#"]     |
-| convention.phpstan_level            | max                           |
+Look into your generated grumphp.yml
 
 
 ### Upgrade to grumphp-config 5
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index d396b7f..3ba01bf 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1,45 +1,10 @@
 parameters:
 	ignoreErrors:
-		-
-			message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
-			count: 1
-			path: src/Composer/Plugin.php
-
 		-
 			message: "#^Cannot access offset 'config\\-default\\-path' on mixed\\.$#"
 			count: 1
 			path: src/Composer/Plugin.php
 
-		-
-			message: "#^Cannot access offset 'imports' on mixed\\.$#"
-			count: 1
-			path: src/Composer/Plugin.php
-
-		-
-			message: "#^Cannot access offset 'parameters' on mixed\\.$#"
-			count: 2
-			path: src/Composer/Plugin.php
-
-		-
-			message: "#^Cannot access offset 'resource' on mixed\\.$#"
-			count: 1
-			path: src/Composer/Plugin.php
-
-		-
-			message: "#^Cannot access offset 0 on mixed\\.$#"
-			count: 1
-			path: src/Composer/Plugin.php
-
-		-
-			message: "#^Cannot access offset mixed on mixed\\.$#"
-			count: 2
-			path: src/Composer/Plugin.php
-
-		-
-			message: "#^Cannot cast mixed to string\\.$#"
-			count: 1
-			path: src/Composer/Plugin.php
-
 		-
 			message: "#^Cannot access offset 'php' on mixed\\.$#"
 			count: 1
diff --git a/phpstan.neon b/phpstan.neon
index 4bbcb1f..9d7a3f6 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -3,5 +3,6 @@ includes:
 
 parameters:
   level: max
+  reportUnmatchedIgnoredErrors: false
   paths:
     - src
diff --git a/src/Composer/Plugin.php b/src/Composer/Plugin.php
index 8fb0d20..363b869 100644
--- a/src/Composer/Plugin.php
+++ b/src/Composer/Plugin.php
@@ -9,11 +9,14 @@
 use Composer\DependencyResolver\Operation\UninstallOperation;
 use Composer\DependencyResolver\Operation\UpdateOperation;
 use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\InstalledVersions;
 use Composer\Installer\PackageEvent;
 use Composer\Installer\PackageEvents;
 use Composer\IO\IOInterface;
 use Composer\Plugin\PluginInterface;
+use Composer\Script\Event;
 use Composer\Script\ScriptEvents;
+use Exception;
 use GrumPHP\Configuration\Configuration;
 use Symfony\Component\Config\FileLocator;
 use Symfony\Component\Routing\Loader\YamlFileLoader;
@@ -51,11 +54,8 @@ public function uninstall(Composer $composer, IOInterface $io): void
     public static function getSubscribedEvents(): array
     {
         return [
-            PackageEvents::POST_PACKAGE_UPDATE => 'heavyProcessing',
-            PackageEvents::POST_PACKAGE_INSTALL => 'heavyProcessing',
-
-            ScriptEvents::POST_INSTALL_CMD => 'heavyProcessing',
             ScriptEvents::POST_UPDATE_CMD => 'heavyProcessing',
+            ScriptEvents::POST_INSTALL_CMD => 'heavyProcessing',
 
             ScriptEvents::POST_AUTOLOAD_DUMP => 'simpleProcessing',
         ];
@@ -64,6 +64,7 @@ public static function getSubscribedEvents(): array
     public function heavyProcessing(): void
     {
         $this->removeOldConfigPath();
+        $this->installTypo3Dependencies();
         $this->createGrumphpConfig();
 
         $this->simpleProcessing();
@@ -101,6 +102,30 @@ private function removeOldConfigPath(): void
         $rootPackage->setExtra($extra);
     }
 
+    private function installTypo3Dependencies(): void
+    {
+        if (!InstalledVersions::isInstalled('typo3/cms-core')) {
+            return;
+        }
+
+        $typo3RelatedPackages = [
+            'saschaegerer/phpstan-typo3' => '>=1.1.2',
+            'ssch/typo3-rector' => '1.0.x-dev',
+        ];
+
+        $changed = false;
+        foreach ($typo3RelatedPackages as $package => $version) {
+            if (!InstalledVersions::isInstalled($package)) {
+                $this->composer->getConfig()->getConfigSource()->addLink('require-dev', $package, $version);
+                $changed = true;
+            }
+        }
+
+        if ($changed) {
+            passthru('composer update ' . implode(' ', array_keys($typo3RelatedPackages)));
+        }
+    }
+
     private function createRectorConfig(): void
     {
         if (!file_exists(getcwd() . '/rector.php')) {
@@ -119,11 +144,14 @@ private function createGrumphpConfig(): void
                     ['resource' => 'vendor/pluswerk/grumphp-config/grumphp.yml'],
                 ],
             ];
-            file_put_contents($grumphpPath, Yaml::dump($defaultImport));
+            file_put_contents($grumphpPath, Yaml::dump($defaultImport, 2, 2));
             $this->message('grumphp.yml file created', 'yellow');
         }
 
         $data = Yaml::parseFile($grumphpPath);
+        assert(is_array($data));
+        $data['parameters'] ??= [];
+        assert(is_array($data['parameters']));
 
         if (($data['imports'][0]['resource'] ?? '') !== 'vendor/pluswerk/grumphp-config/grumphp.yml') {
             return;
@@ -132,12 +160,15 @@ private function createGrumphpConfig(): void
         $changed = false;
 
         $templateData = Yaml::parseFile($grumphpTemplatePath);
-        foreach ($templateData['parameters'] ?? [] as $key => $value) {
+        assert(is_array($templateData));
+        $templateData['parameters'] ??= [];
+        assert(is_array($templateData['parameters']));
+        foreach ($templateData['parameters'] as $key => $value) {
             if (!str_starts_with((string)$key, 'convention.')) {
                 continue;
             }
 
-            if (($data['parameters'][$key] ?? null) === $value) {
+            if (array_key_exists((string)$key, $data['parameters'])) {
                 continue;
             }
 
@@ -146,7 +177,7 @@ private function createGrumphpConfig(): void
         }
 
         if ($changed) {
-            file_put_contents($grumphpPath, Yaml::dump($data));
+            file_put_contents($grumphpPath, Yaml::dump($data, 2, 2));
             $this->message('added some default conventions to grumphp.yml', 'yellow');
         }
     }

From dfeaa429f887c4332ecbe1728888bdc831e8175e Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Fri, 2 Dec 2022 11:39:33 +0100
Subject: [PATCH 5/8] Set parallel timeout to default

---
 rector.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rector.php b/rector.php
index 3ddaa30..13f0f89 100644
--- a/rector.php
+++ b/rector.php
@@ -6,7 +6,7 @@
 use Rector\Config\RectorConfig;
 
 return static function (RectorConfig $rectorConfig): void {
-    $rectorConfig->parallel(240);
+    $rectorConfig->parallel();
     $rectorConfig->importNames();
     $rectorConfig->importShortClasses();
 

From f417a83214bf78595be8572c9f682aaaa672510f Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Mon, 5 Dec 2022 13:56:22 +0100
Subject: [PATCH 6/8] =?UTF-8?q?=E2=9C=A8=20add=20TYPO3=20to=20rector.php?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore              |  1 +
 composer.json           | 12 +++++---
 rector.php              |  6 ++++
 src/Composer/Plugin.php | 10 +++---
 src/RectorSettings.php  | 67 ++++++++++++++++++++++++++++++++++++-----
 5 files changed, 80 insertions(+), 16 deletions(-)

diff --git a/.gitignore b/.gitignore
index a784fd3..501d35c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 /composer.lock
 
 .idea
+var
diff --git a/composer.json b/composer.json
index 2fc4106..9fb5257 100644
--- a/composer.json
+++ b/composer.json
@@ -17,8 +17,10 @@
       "*" : "dist"
     },
     "allow-plugins" : {
-      "phpro/grumphp" : true,
-      "phpstan/extension-installer" : true
+      "phpro/grumphp": true,
+      "phpstan/extension-installer": true,
+      "typo3/cms-composer-installers": true,
+      "typo3/class-alias-loader": true
     }
   },
   "support" : {
@@ -40,10 +42,12 @@
     "php-parallel-lint/php-parallel-lint" : "^1.3.2",
     "phpstan/phpstan" : "^1.8.8",
     "phpstan/extension-installer" : "^1.1",
-    "rector/rector" : "^0.14.5",
+    "rector/rector" : "^0.14.8",
     "palpalani/grumphp-rector-task" : "^0.8.6",
     "composer/semver" : "^3.3.0",
-    "symfony/yaml" : "^5.4.0 || ^6.0"
+    "symfony/yaml" : "^5.4.0 || ^6.0",
+    "saschaegerer/phpstan-typo3": "^1.8.2",
+    "ssch/typo3-rector": "^1.0.6"
   },
   "extra" : {
     "class" : "PLUS\\GrumPHPConfig\\Composer\\Plugin"
diff --git a/rector.php b/rector.php
index 13f0f89..9276b21 100644
--- a/rector.php
+++ b/rector.php
@@ -4,11 +4,14 @@
 
 use PLUS\GrumPHPConfig\RectorSettings;
 use Rector\Config\RectorConfig;
+use Rector\Caching\ValueObject\Storage\FileCacheStorage;
 
 return static function (RectorConfig $rectorConfig): void {
     $rectorConfig->parallel();
     $rectorConfig->importNames();
     $rectorConfig->importShortClasses();
+    $rectorConfig->cacheClass(FileCacheStorage::class);
+    $rectorConfig->cacheDirectory('./var/cache/rector');
 
     $rectorConfig->paths(
         array_filter([
@@ -22,6 +25,8 @@
     $rectorConfig->sets(
         [
             ...RectorSettings::sets(),
+            //    the filesystem caching disables parallel execution, but why?
+            ...RectorSettings::setsTypo3(),
         ]
     );
 
@@ -30,6 +35,7 @@
     $rectorConfig->skip(
         [
             ...RectorSettings::skip(),
+            ...RectorSettings::skipTypo3(),
 
             /**
              * rector should not touch these files
diff --git a/src/Composer/Plugin.php b/src/Composer/Plugin.php
index 363b869..b8503b1 100644
--- a/src/Composer/Plugin.php
+++ b/src/Composer/Plugin.php
@@ -16,6 +16,7 @@
 use Composer\Plugin\PluginInterface;
 use Composer\Script\Event;
 use Composer\Script\ScriptEvents;
+use Composer\Semver\VersionParser;
 use Exception;
 use GrumPHP\Configuration\Configuration;
 use Symfony\Component\Config\FileLocator;
@@ -109,20 +110,21 @@ private function installTypo3Dependencies(): void
         }
 
         $typo3RelatedPackages = [
-            'saschaegerer/phpstan-typo3' => '>=1.1.2',
-            'ssch/typo3-rector' => '1.0.x-dev',
+            'saschaegerer/phpstan-typo3' => '^1.8.2',
+            'ssch/typo3-rector' => '^1.0.6',
         ];
 
         $changed = false;
         foreach ($typo3RelatedPackages as $package => $version) {
-            if (!InstalledVersions::isInstalled($package)) {
+            if (!InstalledVersions::isInstalled($package) || !InstalledVersions::satisfies(new VersionParser(), $package, $version)) {
                 $this->composer->getConfig()->getConfigSource()->addLink('require-dev', $package, $version);
+                $this->message(sprintf('installing %s in version %s for pluswerk/grumphp-config', $package, $version), 'yellow');
                 $changed = true;
             }
         }
 
         if ($changed) {
-            passthru('composer update ' . implode(' ', array_keys($typo3RelatedPackages)));
+            passthru('composer update -W ' . implode(' ', array_keys($typo3RelatedPackages)));
         }
     }
 
diff --git a/src/RectorSettings.php b/src/RectorSettings.php
index cc87b11..8698dcd 100644
--- a/src/RectorSettings.php
+++ b/src/RectorSettings.php
@@ -4,6 +4,7 @@
 
 namespace PLUS\GrumPHPConfig;
 
+use Composer\InstalledVersions;
 use Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector;
 use Rector\CodeQuality\Rector\If_\SimplifyIfElseToTernaryRector;
 use Rector\CodeQuality\Rector\Isset_\IssetOnPropertyObjectToPropertyExistsRector;
@@ -19,19 +20,27 @@
 use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenRector;
 use Rector\Set\ValueObject\LevelSetList;
 use Rector\Set\ValueObject\SetList;
+use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictGetterMethodReturnTypeRector;
+use Ssch\TYPO3Rector\Rector\Migrations\RenameClassMapAliasRector;
+use Ssch\TYPO3Rector\Set\Typo3LevelSetList;
+use Ssch\TYPO3Rector\Set\Typo3SetList;
 
-class RectorSettings
+final class RectorSettings
 {
     /**
      * @return array<int,string>
      */
-    public static function sets(): array
+    public static function sets(bool $entirety = false): array
     {
         $phpVersion = VersionUtility::getMinimalPhpVersion() ?? PHP_MAJOR_VERSION . PHP_MINOR_VERSION;
-        $phpfile = constant(LevelSetList::class . '::UP_TO_PHP_' . $phpVersion);
-        assert(is_string($phpfile));
+        $phpFile = constant(SetList::class . '::PHP_' . $phpVersion);
+        if ($entirety) {
+            $phpFile = constant(LevelSetList::class . '::UP_TO_PHP_' . $phpVersion);
+        }
 
-        return [
+        assert(is_string($phpFile));
+
+        return array_filter([
             // SetList::ACTION_INJECTION_TO_CONSTRUCTOR_INJECTION, // NO
             SetList::CODE_QUALITY, // YES
             SetList::CODING_STYLE, // YES
@@ -40,7 +49,7 @@ public static function sets(): array
             //SetList::MONOLOG_20, // no usage
             //SetList::MYSQL_TO_MYSQLI, // no usage
             //SetList::NAMING, //NO is not good
-            $phpfile,
+            $phpFile,
             //SetList::PHP_52, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
             //SetList::PHP_53, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
             //SetList::PHP_54, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
@@ -55,11 +64,31 @@ public static function sets(): array
             //SetList::PHP_81, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
             //SetList::PHP_82, // YES, included in LevelSetList::class . '::UP_TO_PHP_' ...
             SetList::PRIVATIZATION, // some things may be bad
-            SetList::PSR_4, // dose nothing?
+            $entirety ? SetList::PSR_4 : null,
             SetList::TYPE_DECLARATION, // YES
             SetList::TYPE_DECLARATION_STRICT, // YES please
             SetList::EARLY_RETURN,  //YES
-            //SetList::RECTOR_CONFIG, // NO
+        ]);
+    }
+
+    /**
+     * @return array<int, string>
+     */
+    public static function setsTypo3(bool $entirety = false): array
+    {
+        if (!InstalledVersions::isInstalled('typo3/cms-core')) {
+            return [];
+        }
+
+        [$typo3MajorVersion] = explode('.', (string)InstalledVersions::getVersion('typo3/cms-core'), 2);
+        $setList = constant(Typo3SetList::class . '::TYPO3_' . $typo3MajorVersion);
+        if ($entirety) {
+            $setList = constant(Typo3LevelSetList::class . '::UP_TO_TYPO3_' . $typo3MajorVersion);
+        }
+
+        assert(is_string($setList));
+        return [
+            $setList,
         ];
     }
 
@@ -138,6 +167,28 @@ public static function skip(): array
              * TO:   property_exists($this, 'x') && $this->x !== null;
              */
             IssetOnPropertyObjectToPropertyExistsRector::class,
+            /**
+             * FROM: * @ var ObjectStorage<Moption>
+             * TO:   * @ var ObjectStorage
+             */
+            TypedPropertyFromStrictGetterMethodReturnTypeRector::class,
+        ];
+    }
+
+    /**
+     * @return array<int, string>
+     */
+    public static function skipTypo3(): array
+    {
+        if (!InstalledVersions::isInstalled('typo3/cms-core')) {
+            return [];
+        }
+
+        return [
+            /**
+             * not used:
+             */
+            RenameClassMapAliasRector::class
         ];
     }
 }

From 805dedb861306af56a18ab2ba1ee439f62f3ca8c Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Wed, 1 Feb 2023 16:17:10 +0100
Subject: [PATCH 7/8] Update VersionUtility.php

---
 src/VersionUtility.php | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/VersionUtility.php b/src/VersionUtility.php
index 6b7430e..141d694 100644
--- a/src/VersionUtility.php
+++ b/src/VersionUtility.php
@@ -4,10 +4,10 @@
 
 namespace PLUS\GrumPHPConfig;
 
-use Composer\InstalledVersions;
 use Composer\Semver\VersionParser;
 
 use function json_decode;
+use function getcwd;
 
 final class VersionUtility
 {
@@ -36,8 +36,7 @@ public static function getMinimalPhpVersion(): ?string
      */
     private static function getRootComposerJsonData(): array
     {
-        $rootPackage = InstalledVersions::getRootPackage();
-        $contents = file_get_contents($rootPackage['install_path'] . 'composer.json');
+        $contents = file_get_contents(getcwd() . '/composer.json');
         return json_decode((string)$contents, true, 512, JSON_THROW_ON_ERROR) ?: [];
     }
 }

From 34415e097973ea3923dcbcc00ff1daa4744ea279 Mon Sep 17 00:00:00 2001
From: Matthias Vogel <m.vogel@andersundsehr.com>
Date: Thu, 2 Feb 2023 12:56:52 +0100
Subject: [PATCH 8/8] =?UTF-8?q?=E2=9C=A8=20add=20new=20config=20handling?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .github/workflows/run-grumphp-test.yml |  4 +--
 composer.json                          | 17 ++++++----
 grumphp.yml                            |  9 +++---
 phpstan-baseline.neon                  |  7 +++-
 rector.php                             |  5 ++-
 src/Composer/Plugin.php                | 17 +++++-----
 src/RectorSettings.php                 |  5 ++-
 src/VersionUtility.php                 | 44 ++++++++++++++++++++++++--
 8 files changed, 77 insertions(+), 31 deletions(-)

diff --git a/.github/workflows/run-grumphp-test.yml b/.github/workflows/run-grumphp-test.yml
index 7472e64..c8f55a7 100644
--- a/.github/workflows/run-grumphp-test.yml
+++ b/.github/workflows/run-grumphp-test.yml
@@ -4,12 +4,12 @@ jobs:
   test:
     runs-on: ubuntu-latest
     strategy:
+      fail-fast: false
       matrix:
         php:
-          - '7.4'
           - '8.0'
           - '8.1'
-          - '8.2-rc'
+          - '8.2'
         composer:
           - install --no-progress --no-scripts -n
           - update --no-progress --no-scripts -n --prefer-lowest
diff --git a/composer.json b/composer.json
index 9fb5257..c232e63 100644
--- a/composer.json
+++ b/composer.json
@@ -32,9 +32,9 @@
     }
   },
   "require" : {
-    "php" : "~7.4 || ~8.0 || ~8.1 || ~8.2",
+    "php" : "~8.0 || ~8.1 || ~8.2",
     "composer-plugin-api" : "^2.1.0",
-    "phpro/grumphp" : "^1.13",
+    "phpro/grumphp" : "^1.15",
     "pluswerk/grumphp-bom-task" : "^7.0.0",
     "pluswerk/grumphp-xliff-task" : "^5.0.0",
     "squizlabs/php_codesniffer" : "^3.7.1",
@@ -42,18 +42,21 @@
     "php-parallel-lint/php-parallel-lint" : "^1.3.2",
     "phpstan/phpstan" : "^1.8.8",
     "phpstan/extension-installer" : "^1.1",
-    "rector/rector" : "^0.14.8",
-    "palpalani/grumphp-rector-task" : "^0.8.6",
+    "rector/rector" : "^0.15.10",
     "composer/semver" : "^3.3.0",
-    "symfony/yaml" : "^5.4.0 || ^6.0",
-    "saschaegerer/phpstan-typo3": "^1.8.2",
-    "ssch/typo3-rector": "^1.0.6"
+    "symfony/yaml" : "^5.4.0 || ^6.0"
   },
   "extra" : {
     "class" : "PLUS\\GrumPHPConfig\\Composer\\Plugin"
   },
   "require-dev" : {
+    "saschaegerer/phpstan-typo3" : "^1.8.2",
+    "ssch/typo3-rector" : "^1.1.3",
     "roave/security-advisories" : "dev-latest",
     "composer/composer" : "^2.4.2"
+  },
+  "require-typo3" : {
+    "saschaegerer/phpstan-typo3" : "^1.8.2",
+    "ssch/typo3-rector" : "^1.1.3"
   }
 }
diff --git a/grumphp.yml b/grumphp.yml
index c4ed91a..697c113 100644
--- a/grumphp.yml
+++ b/grumphp.yml
@@ -7,6 +7,7 @@ parameters:
   convention.phpcslint_ignore_pattern: []
   convention.xlifflint_ignore_pattern: []
   convention.rector_ignore_pattern: []
+  convention.rector_enabled: true
   convention.rector_config: 'rector.php'
   convention.rector_clear-cache: false
   convention.phpstan_level: max
@@ -54,14 +55,12 @@ grumphp:
       metadata:
         priority: 1
     rector:
-      whitelist_patterns: [ ]
+      metadata:
+        enabled: "%convention.rector_enabled%"
       config: "%convention.rector_config%"
       triggered_by: [ 'php' ]
-      clear-cache: "%convention.rector_clear-cache%"
+      clear_cache: "%convention.rector_clear-cache%"
       ignore_patterns: "%convention.rector_ignore_pattern%"
-      no-progress-bar: false
-      files_on_pre_commit: false
   extensions:
     - PLUS\GrumPHPBomTask\ExtensionLoader
     - PLUS\GrumPHPXliffTask\ExtensionLoader
-    - palPalani\GrumPhpRectorTask\ExtensionLoader
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 3ba01bf..8324867 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -11,6 +11,11 @@ parameters:
 			path: src/VersionUtility.php
 
 		-
-			message: "#^Method PLUS\\\\GrumPHPConfig\\\\VersionUtility\\:\\:getRootComposerJsonData\\(\\) should return array but returns mixed\\.$#"
+			message: "#^Method PLUS\\\\GrumPHPConfig\\\\VersionUtility\\:\\:getRequire\\(\\) should return array\\<string, string\\> but returns mixed\\.$#"
+			count: 1
+			path: src/VersionUtility.php
+
+		-
+			message: "#^Method PLUS\\\\GrumPHPConfig\\\\VersionUtility\\:\\:readJson\\(\\) should return array\\<string, mixed\\> but returns mixed\\.$#"
 			count: 1
 			path: src/VersionUtility.php
diff --git a/rector.php b/rector.php
index 9276b21..cb9a514 100644
--- a/rector.php
+++ b/rector.php
@@ -24,9 +24,8 @@
     // define sets of rules
     $rectorConfig->sets(
         [
-            ...RectorSettings::sets(),
-            //    the filesystem caching disables parallel execution, but why?
-            ...RectorSettings::setsTypo3(),
+            ...RectorSettings::sets(true),
+            ...RectorSettings::setsTypo3(false),
         ]
     );
 
diff --git a/src/Composer/Plugin.php b/src/Composer/Plugin.php
index b8503b1..a0812af 100644
--- a/src/Composer/Plugin.php
+++ b/src/Composer/Plugin.php
@@ -19,6 +19,7 @@
 use Composer\Semver\VersionParser;
 use Exception;
 use GrumPHP\Configuration\Configuration;
+use PLUS\GrumPHPConfig\VersionUtility;
 use Symfony\Component\Config\FileLocator;
 use Symfony\Component\Routing\Loader\YamlFileLoader;
 use Symfony\Component\Yaml\Yaml;
@@ -50,13 +51,16 @@ public function uninstall(Composer $composer, IOInterface $io): void
     }
 
     /**
-     * @return array<string, string>
+     * @return array<string, string|array{0:string, 1:int}>
      */
     public static function getSubscribedEvents(): array
     {
+        $priority = 1000;
+        // priority higher than phpro/grumphp so it dose not ask if you want to create a grumphp.yml,
+        // as we do that for you
         return [
-            ScriptEvents::POST_UPDATE_CMD => 'heavyProcessing',
-            ScriptEvents::POST_INSTALL_CMD => 'heavyProcessing',
+            ScriptEvents::POST_UPDATE_CMD => ['heavyProcessing', $priority],
+            ScriptEvents::POST_INSTALL_CMD => ['heavyProcessing', $priority],
 
             ScriptEvents::POST_AUTOLOAD_DUMP => 'simpleProcessing',
         ];
@@ -109,10 +113,7 @@ private function installTypo3Dependencies(): void
             return;
         }
 
-        $typo3RelatedPackages = [
-            'saschaegerer/phpstan-typo3' => '^1.8.2',
-            'ssch/typo3-rector' => '^1.0.6',
-        ];
+        $typo3RelatedPackages = VersionUtility::getRequire('typo3');
 
         $changed = false;
         foreach ($typo3RelatedPackages as $package => $version) {
@@ -124,7 +125,7 @@ private function installTypo3Dependencies(): void
         }
 
         if ($changed) {
-            passthru('composer update -W ' . implode(' ', array_keys($typo3RelatedPackages)));
+            passthru('composer update -W --no-scripts ' . implode(' ', array_keys($typo3RelatedPackages)));
         }
     }
 
diff --git a/src/RectorSettings.php b/src/RectorSettings.php
index 8698dcd..e913512 100644
--- a/src/RectorSettings.php
+++ b/src/RectorSettings.php
@@ -66,7 +66,6 @@ public static function sets(bool $entirety = false): array
             SetList::PRIVATIZATION, // some things may be bad
             $entirety ? SetList::PSR_4 : null,
             SetList::TYPE_DECLARATION, // YES
-            SetList::TYPE_DECLARATION_STRICT, // YES please
             SetList::EARLY_RETURN,  //YES
         ]);
     }
@@ -76,11 +75,11 @@ public static function sets(bool $entirety = false): array
      */
     public static function setsTypo3(bool $entirety = false): array
     {
-        if (!InstalledVersions::isInstalled('typo3/cms-core')) {
+        if (!VersionUtility::isInstalled('typo3/cms-core')) {
             return [];
         }
 
-        [$typo3MajorVersion] = explode('.', (string)InstalledVersions::getVersion('typo3/cms-core'), 2);
+        [$typo3MajorVersion] = explode('.', (string)VersionUtility::getVersion('typo3/cms-core'), 2);
         $setList = constant(Typo3SetList::class . '::TYPO3_' . $typo3MajorVersion);
         if ($entirety) {
             $setList = constant(Typo3LevelSetList::class . '::UP_TO_TYPO3_' . $typo3MajorVersion);
diff --git a/src/VersionUtility.php b/src/VersionUtility.php
index 141d694..f2afcf2 100644
--- a/src/VersionUtility.php
+++ b/src/VersionUtility.php
@@ -11,6 +11,11 @@
 
 final class VersionUtility
 {
+    /**
+     * @var array{versions?: array<string, mixed>}
+     */
+    private static array $installed = [];
+
     public static function getMinimalPhpVersion(): ?string
     {
         $phpVersionConstrain = self::getRootComposerJsonData()['require']['php'] ?? false;
@@ -36,7 +41,42 @@ public static function getMinimalPhpVersion(): ?string
      */
     private static function getRootComposerJsonData(): array
     {
-        $contents = file_get_contents(getcwd() . '/composer.json');
-        return json_decode((string)$contents, true, 512, JSON_THROW_ON_ERROR) ?: [];
+        return self::readJson(getcwd() . '/composer.json');
+    }
+
+    /**
+     * we can not use \Composer\InstalledVersions::isInstalled because rector has its own vendor dir with different dependencies.
+     */
+    public static function isInstalled(string $packageName): bool
+    {
+        self::$installed = self::$installed ?: require getcwd() . '/vendor/composer/installed.php';
+        return isset(self::$installed['versions'][$packageName]) && empty(self::$installed['versions'][$packageName]['dev_requirement']);
+    }
+
+    /**
+     * we can not use \Composer\InstalledVersions::getVersion because rector has its own vendor dir with different dependencies.
+     */
+    public static function getVersion(string $packageName): ?string
+    {
+        self::$installed = self::$installed ?: require getcwd() . '/vendor/composer/installed.php';
+        return self::$installed['versions'][$packageName]['version'] ?? null;
+    }
+
+    /**
+     * @return array<string, string>
+     */
+    public static function getRequire(string $requireSectionName): array
+    {
+        $file = dirname(__DIR__) . '/composer.json';
+        $composerJsonData = self::readJson($file);
+        return $composerJsonData['require-' . $requireSectionName] ?? [];
+    }
+
+    /**
+     * @return array<string, mixed>
+     */
+    private static function readJson(string $file): array
+    {
+        return json_decode((string)(file_get_contents($file)), true, 512, JSON_THROW_ON_ERROR) ?: [];
     }
 }