diff --git a/.github/workflows/compat_test.yaml b/.github/workflows/compat_test.yaml deleted file mode 100644 index d44f4f85736..00000000000 --- a/.github/workflows/compat_test.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# see https://github.com/rectorphp/rector/issues/9416 -name: PHPUnit and PHPStan Compat Test - -on: - push: - branches: - - main - pull_request: null - -jobs: - compat_test: - runs-on: ubuntu-latest - - steps: - - - uses: shivammathur/setup-php@v2 - with: - php-version: 8.3 - coverage: none - - - run: composer create-project "rector/rector-compat-tests:dev-main" . - - - run: vendor/bin/phpunit tests/PHPStan - - - run: vendor/bin/phpunit tests/Rector - - - run: vendor/bin/phpunit tests - - - run: vendor/bin/phpunit diff --git a/.github/workflows/phpunit_and_phpstan_autoload_compat_test.yaml b/.github/workflows/phpunit_and_phpstan_autoload_compat_test.yaml new file mode 100644 index 00000000000..85f737b3bfc --- /dev/null +++ b/.github/workflows/phpunit_and_phpstan_autoload_compat_test.yaml @@ -0,0 +1,60 @@ +# see https://github.com/rectorphp/rector/issues/9416 +# compat tests project lives in /compat-tests +name: PHPUnit and PHPStan Autoload Compat Test + +on: + push: + branches: + - main + pull_request: null + schedule: + - cron: '0 6 * * *' + +jobs: + compat_test: + strategy: + fail-fast: false + matrix: + actions: + - + name: 'Rector dev + PHPUnit 10' + run: composer require "phpunit/phpunit:10.*" -W + php: 8.2 + - + name: 'Rector dev + PHPUnit 11' + run: composer require "phpunit/phpunit:11.*" -W + php: 8.2 + - + name: 'Rector dev + PHPUnit 12' + run: composer require "phpunit/phpunit:12.*" -W + php: 8.3 + + name: ${{ matrix.actions.name }} + + runs-on: ubuntu-latest + + defaults: + run: + working-directory: compat-tests + + steps: + - uses: actions/checkout@v4 + + - + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.actions.php }} + coverage: none + + - + uses: "ramsey/composer-install@v4" + with: + working-directory: compat-tests + + - run: ${{ matrix.actions.run }} + + - run: vendor/bin/phpunit tests/Rector + + - run: vendor/bin/phpunit tests/PHPStan + + - run: vendor/bin/phpunit diff --git a/compat-tests/.editorconfig b/compat-tests/.editorconfig new file mode 100644 index 00000000000..bec95c4494f --- /dev/null +++ b/compat-tests/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 diff --git a/compat-tests/.gitignore b/compat-tests/.gitignore new file mode 100644 index 00000000000..b70e169059d --- /dev/null +++ b/compat-tests/.gitignore @@ -0,0 +1,4 @@ +composer.lock +/vendor + +.phpunit.result.cache diff --git a/compat-tests/README.md b/compat-tests/README.md new file mode 100644 index 00000000000..cde90af5c73 --- /dev/null +++ b/compat-tests/README.md @@ -0,0 +1,22 @@ +# Rector Compatibility Tests + +A small test harness that verifies [Rector](https://github.com/rectorphp/rector) keeps working alongside other tools in the PHP static-analysis ecosystem. + +It runs a minimal real-world setup — a custom Rector rule, a custom PHPStan rule, and a PHPUnit test suite — against multiple combinations of dependencies in CI, to catch breakage early. + +## What it checks + +* A custom **Rector** rule (`MakeClassFinalRector`, `UseGetArgRector`) loads and applies correctly. +* A custom **PHPStan** rule (`CustomPHPStanRule`) loads and runs alongside Rector. +* **PHPUnit 10, 11, and 12** all work with Rector's preloaded dependencies. +* Manually including **`nikic/php-parser`** in user code does not conflict with the copy Rector ships. +* `rector-laravel` + `nikic/php-parser` upgrade scenarios run cleanly (see [rectorphp/rector#9470](https://github.com/rectorphp/rector/issues/9470)). + +This project lives inside `rector/rector-src` (under `compat-tests/`) and pulls in `rector/rector:dev-main` as a downstream dependency, so the compat suite is maintained in one place. It mirrors the standalone [rectorphp/rector-compat-tests](https://github.com/rectorphp/rector-compat-tests). + +## How it runs + +GitHub Actions runs the matrix daily (`0 6 * * *`) and on every push/PR, from the repository root `.github/workflows`: + +* `compat_test.yaml` — Rector dev + PHPUnit 10 / 11 / 12 +* `rector_laravel_rector_dev.yaml` — Rector + rector-laravel + php-parser diff --git a/compat-tests/composer.json b/compat-tests/composer.json new file mode 100644 index 00000000000..f80e8e11cde --- /dev/null +++ b/compat-tests/composer.json @@ -0,0 +1,24 @@ +{ + "name": "rector/rector-compat-tests", + "license": "MIT", + "description": "Tests for compatibility with PHPStand and PHPUnit preload magic", + "require-dev": { + "php": "^8.2", + "phpunit/phpunit": "10.*|11.*|12.*", + "nikic/php-parser": "5.4.*|5.5.*|5.6.*|5.7.*", + "rector/rector": "dev-main as 2.4.4", + "phpstan/phpstan": "^2.2", + "driftingly/rector-laravel": "^2.4" + }, + "autoload": { + "psr-4": { + "Rector\\RectorCompatTests\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Rector\\RectorCompatTests\\Tests\\": "tests", + "Fixture\\": "fixture" + } + } +} diff --git a/compat-tests/fixture/FixtureWithFuncCall.php b/compat-tests/fixture/FixtureWithFuncCall.php new file mode 100644 index 00000000000..902dca44d94 --- /dev/null +++ b/compat-tests/fixture/FixtureWithFuncCall.php @@ -0,0 +1,11 @@ + + + + + + tests + + + diff --git a/compat-tests/rector.php b/compat-tests/rector.php new file mode 100644 index 00000000000..7a639f351c5 --- /dev/null +++ b/compat-tests/rector.php @@ -0,0 +1,17 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + __DIR__ . '/fixture', + ]) + ->withRules([UseGetArgRector::class]) + ->withTypeCoverageLevel(0) + ->withDeadCodeLevel(0) + ->withCodeQualityLevel(0); diff --git a/compat-tests/src/PHPStan/CustomPHPStanRule.php b/compat-tests/src/PHPStan/CustomPHPStanRule.php new file mode 100644 index 00000000000..4e528849d47 --- /dev/null +++ b/compat-tests/src/PHPStan/CustomPHPStanRule.php @@ -0,0 +1,42 @@ + + */ +final class CustomPHPStanRule implements Rule +{ + public const ERROR_MESSAGE = 'Class "%s" is not final.'; + + public function getNodeType(): string + { + return Class_::class; + } + + /** + * @param Class_ $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + if ($node->isFinal()) { + return []; + } + + $ruleErrorMessage = RuleErrorBuilder::message( + sprintf(self::ERROR_MESSAGE, $node->name->toString()) + )->build(); + + return [$ruleErrorMessage]; + } +} diff --git a/compat-tests/src/Rector/MakeClassFinalRector.php b/compat-tests/src/Rector/MakeClassFinalRector.php new file mode 100644 index 00000000000..a675baeb453 --- /dev/null +++ b/compat-tests/src/Rector/MakeClassFinalRector.php @@ -0,0 +1,35 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node) + { + if ($node->isFinal()) { + return null; + } + + $node->flags |= Modifiers::FINAL; + + return $node; + } +} diff --git a/compat-tests/src/Rector/UseGetArgRector.php b/compat-tests/src/Rector/UseGetArgRector.php new file mode 100644 index 00000000000..305bd2f7e47 --- /dev/null +++ b/compat-tests/src/Rector/UseGetArgRector.php @@ -0,0 +1,41 @@ +> + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node) + { + // here we should load Rector's php-parser 5.6, that already has getArg() method + $firstArg = $node->getArg('', 0); + if (! $firstArg instanceof Arg) { + return null;; + } + + $firstArg->value = new String_('changed_value'); + + return $node; + } +} diff --git a/compat-tests/tests/ConflictingPhpParserLibraryTest.php b/compat-tests/tests/ConflictingPhpParserLibraryTest.php new file mode 100644 index 00000000000..d2eca54f610 --- /dev/null +++ b/compat-tests/tests/ConflictingPhpParserLibraryTest.php @@ -0,0 +1,20 @@ += 12) { + $this->markTestSkipped('This test requires PHPUnit < 12'); + } + + include_once dirname(__DIR__, 1) . '/vendor/nikic/php-parser/lib/PhpParser/Parser.php'; + + $this->assertTrue(true); + } +} \ No newline at end of file diff --git a/compat-tests/tests/PHPStan/CustomPHPStanRule/CustomPHPStanRuleTest.php b/compat-tests/tests/PHPStan/CustomPHPStanRule/CustomPHPStanRuleTest.php new file mode 100644 index 00000000000..9c10ac2d360 --- /dev/null +++ b/compat-tests/tests/PHPStan/CustomPHPStanRule/CustomPHPStanRuleTest.php @@ -0,0 +1,24 @@ +analyse([__DIR__ . '/Fixture/NonFinalClass.php'], [ + [sprintf(CustomPHPStanRule::ERROR_MESSAGE, "NonFinalClass"), 7] + ]); + } + + protected function getRule(): Rule + { + return new CustomPHPStanRule(); + } +} diff --git a/compat-tests/tests/PHPStan/CustomPHPStanRule/Fixture/NonFinalClass.php b/compat-tests/tests/PHPStan/CustomPHPStanRule/Fixture/NonFinalClass.php new file mode 100644 index 00000000000..fbfc5fa60d4 --- /dev/null +++ b/compat-tests/tests/PHPStan/CustomPHPStanRule/Fixture/NonFinalClass.php @@ -0,0 +1,10 @@ + +----- + diff --git a/compat-tests/tests/Rector/MakeClassFinalRector/MakeClassFinalRectorTest.php b/compat-tests/tests/Rector/MakeClassFinalRector/MakeClassFinalRectorTest.php new file mode 100644 index 00000000000..a2d8b1a48fa --- /dev/null +++ b/compat-tests/tests/Rector/MakeClassFinalRector/MakeClassFinalRectorTest.php @@ -0,0 +1,18 @@ +doTestFile(__DIR__ . '/Fixture/some_non_final_class.php.inc'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/compat-tests/tests/Rector/MakeClassFinalRector/config/configured_rule.php b/compat-tests/tests/Rector/MakeClassFinalRector/config/configured_rule.php new file mode 100644 index 00000000000..acb5c3999dc --- /dev/null +++ b/compat-tests/tests/Rector/MakeClassFinalRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([MakeClassFinalRector::class]);