From 513b0440950fd6d3d065535840476b36bde1400b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 13 Sep 2026 12:01:18 +0200 Subject: [PATCH] Rewrite argtyper in Go using php-parser-in-go Replace the PHP + PHPStan + Rector implementation with a self-contained Go tool built on rectorphp/php-parser-in-go. It collects the types of literal arguments passed into statically resolvable call targets (new X(), X::m(), self::m(), $this->m() and plain function calls), groups them per parameter position, and fills in missing parameter type declarations. Ambiguous types are skipped, a single type plus null becomes nullable. No type inference engine is used, so only literal values and statically resolvable targets are handled. --- .github/workflows/bare_run.yaml | 26 --- .github/workflows/build.yaml | 25 +++ .github/workflows/code_analysis.yaml | 62 ------ .github/workflows/downgraded_release.yaml | 65 ------ .github/workflows/run_on_project.yaml | 35 ---- .gitignore | 12 +- README.md | 86 ++++---- bin/argtyper | 4 - bin/argtyper.php | 14 -- bin/autoload.php | 16 -- build/rector-downgrade-php-72.php | 9 - build/target-repository/.github/FUNDING.yml | 3 - .../.github/workflows/auto_closer.yaml | 23 --- build/target-repository/composer.json | 19 -- composer-dependency-analyser.php | 10 - composer.json | 65 ------ config/phpstan-collecting-data.neon | 22 -- config/rector-argtyper.php | 25 --- ecs.php | 14 -- func-call-collected-data.json | 10 - go.mod | 5 + go.sum | 8 + internal/aggregate/aggregate.go | 113 +++++++++++ internal/aggregate/aggregate_test.go | 77 +++++++ internal/apply/apply.go | 151 ++++++++++++++ internal/apply/apply_test.go | 119 +++++++++++ internal/collect/collect.go | 191 ++++++++++++++++++ internal/collect/collect_test.go | 97 +++++++++ internal/finder/finder.go | 52 +++++ internal/finder/finder_test.go | 48 +++++ internal/phpast/phpast.go | 105 ++++++++++ main.go | 89 ++++++++ phpstan.neon | 33 --- phpunit.xml | 14 -- prefix-code.sh | 47 ----- rector.php | 29 --- scoper.php | 20 -- src/Command/AddTypesCommand.php | 137 ------------- .../CallLikeTypesConfigurationProvider.php | 151 -------------- .../FuncCallTypesConfigurationProvider.php | 110 ---------- src/DependencyInjection/ContainerFactory.php | 68 ------- src/Enum/ConfigFilePath.php | 18 -- src/Exception/NotImplementedException.php | 11 - src/Helpers/FilesLoader.php | 52 ----- src/Helpers/ProjectDirectoryFinder.php | 66 ------ src/Helpers/ReflectionChecker.php | 46 ----- .../CallLikeClassReflectionResolver.php | 63 ------ ...eAllErrorsExceptArgTyperErrorExtension.php | 19 -- .../Rule/CollectCallLikeArgTypesRule.php | 104 ---------- .../Rule/CollectFuncCallArgTypesRule.php | 80 -------- src/PHPStan/TypeMapper.php | 98 --------- src/Process/ProcessRunner.php | 36 ---- .../AddClassMethodParamTypeRector.php | 181 ----------------- .../AddParamIterableDocblockTypeRector.php | 127 ------------ .../Function_/AddFunctionParamTypeRector.php | 102 ---------- src/Rector/TypeMapper/DocStringTypeMapper.php | 38 ---- src/Rector/TypeResolver.php | 51 ----- src/Rector/ValueObject/ClassMethodType.php | 47 ----- src/Rector/ValueObject/FuncCallType.php | 41 ---- src/ValueObject/Project.php | 50 ----- .../CollectCallLikeArgTypesRuleTest.php | 128 ------------ .../Fixture/AllowMissingParentType.php | 22 -- .../Fixture/ConstructorArgs.php | 19 -- .../Fixture/FloatAsInt.php | 17 -- .../Fixture/MethodCalledArgs.php | 20 -- .../Source/ClassWithMissingParentType.php | 12 -- .../Source/ObjectWithConstructor.php | 12 -- .../Source/SomeObject.php | 22 -- .../CollectFuncCallArgTypesRuleTest.php | 62 ------ .../Fixture/SimpleFunctionCall.php | 15 -- .../Source/some_function.php | 9 - .../AddClassMethodParamTypeRectorTest.php | 57 ------ .../add_nullable_for_default_null.php.inc | 25 --- ..._nullable_scalar_from_null_default.php.inc | 25 --- .../Fixture/keep_date_time_interface.php.inc | 10 - .../keep_nullable_date_time_interface.php.inc | 10 - .../keep_nullable_scalar_param.php.inc | 10 - .../skip_int_to_float_override.php.inc | 10 - .../Fixture/skip_parent_contract.php.inc | 12 -- .../Source/ParentContractInterface.php | 8 - .../config/configured_rule.php | 9 - ...AddParamIterableDocblockTypeRectorTest.php | 43 ---- .../Fixture/some_fixture.php.inc | 28 --- .../config/configured_rule.php | 9 - .../AddFunctionParamTypeRectorTest.php | 50 ----- .../Fixture/default_null_function.php.inc | 19 -- .../Fixture/simple_function.php.inc | 19 -- .../config/configured_rule.php | 9 - tests/ValueObject/Fixture/src/some_file.php | 1 - tests/ValueObject/Fixture/tests/some_file.php | 1 - tests/ValueObject/Fixture/vendor/autoload.php | 3 - tests/ValueObject/ProjectTest.php | 40 ---- 92 files changed, 1117 insertions(+), 3058 deletions(-) delete mode 100644 .github/workflows/bare_run.yaml create mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/code_analysis.yaml delete mode 100644 .github/workflows/downgraded_release.yaml delete mode 100644 .github/workflows/run_on_project.yaml delete mode 100755 bin/argtyper delete mode 100755 bin/argtyper.php delete mode 100755 bin/autoload.php delete mode 100644 build/rector-downgrade-php-72.php delete mode 100644 build/target-repository/.github/FUNDING.yml delete mode 100644 build/target-repository/.github/workflows/auto_closer.yaml delete mode 100644 build/target-repository/composer.json delete mode 100644 composer-dependency-analyser.php delete mode 100644 composer.json delete mode 100644 config/phpstan-collecting-data.neon delete mode 100644 config/rector-argtyper.php delete mode 100644 ecs.php delete mode 100644 func-call-collected-data.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/aggregate/aggregate.go create mode 100644 internal/aggregate/aggregate_test.go create mode 100644 internal/apply/apply.go create mode 100644 internal/apply/apply_test.go create mode 100644 internal/collect/collect.go create mode 100644 internal/collect/collect_test.go create mode 100644 internal/finder/finder.go create mode 100644 internal/finder/finder_test.go create mode 100644 internal/phpast/phpast.go create mode 100644 main.go delete mode 100644 phpstan.neon delete mode 100644 phpunit.xml delete mode 100644 prefix-code.sh delete mode 100644 rector.php delete mode 100644 scoper.php delete mode 100644 src/Command/AddTypesCommand.php delete mode 100644 src/Configuration/CallLikeTypesConfigurationProvider.php delete mode 100644 src/Configuration/FuncCallTypesConfigurationProvider.php delete mode 100644 src/DependencyInjection/ContainerFactory.php delete mode 100644 src/Enum/ConfigFilePath.php delete mode 100644 src/Exception/NotImplementedException.php delete mode 100644 src/Helpers/FilesLoader.php delete mode 100644 src/Helpers/ProjectDirectoryFinder.php delete mode 100644 src/Helpers/ReflectionChecker.php delete mode 100644 src/PHPStan/CallLikeClassReflectionResolver.php delete mode 100644 src/PHPStan/IgnoreAllErrorsExceptArgTyperErrorExtension.php delete mode 100644 src/PHPStan/Rule/CollectCallLikeArgTypesRule.php delete mode 100644 src/PHPStan/Rule/CollectFuncCallArgTypesRule.php delete mode 100644 src/PHPStan/TypeMapper.php delete mode 100644 src/Process/ProcessRunner.php delete mode 100644 src/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector.php delete mode 100644 src/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector.php delete mode 100644 src/Rector/Rector/Function_/AddFunctionParamTypeRector.php delete mode 100644 src/Rector/TypeMapper/DocStringTypeMapper.php delete mode 100644 src/Rector/TypeResolver.php delete mode 100644 src/Rector/ValueObject/ClassMethodType.php delete mode 100644 src/Rector/ValueObject/FuncCallType.php delete mode 100644 src/ValueObject/Project.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/CollectCallLikeArgTypesRuleTest.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/AllowMissingParentType.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/ConstructorArgs.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/FloatAsInt.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/MethodCalledArgs.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/Source/ClassWithMissingParentType.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/Source/ObjectWithConstructor.php delete mode 100644 tests/PHPStan/CollectCallLikeArgTypesRule/Source/SomeObject.php delete mode 100644 tests/PHPStan/CollectFuncCallArgTypesRule/CollectFuncCallArgTypesRuleTest.php delete mode 100644 tests/PHPStan/CollectFuncCallArgTypesRule/Fixture/SimpleFunctionCall.php delete mode 100644 tests/PHPStan/CollectFuncCallArgTypesRule/Source/some_function.php delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/AddClassMethodParamTypeRectorTest.php delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_for_default_null.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_scalar_from_null_default.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/keep_date_time_interface.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/keep_nullable_date_time_interface.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/keep_nullable_scalar_param.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/skip_int_to_float_override.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/skip_parent_contract.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Source/ParentContractInterface.php delete mode 100644 tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/config/configured_rule.php delete mode 100644 tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/AddParamIterableDocblockTypeRectorTest.php delete mode 100644 tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/Fixture/some_fixture.php.inc delete mode 100644 tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/config/configured_rule.php delete mode 100644 tests/Rector/Rector/Function_/AddFunctionParamTypeRector/AddFunctionParamTypeRectorTest.php delete mode 100644 tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/default_null_function.php.inc delete mode 100644 tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/simple_function.php.inc delete mode 100644 tests/Rector/Rector/Function_/AddFunctionParamTypeRector/config/configured_rule.php delete mode 100644 tests/ValueObject/Fixture/src/some_file.php delete mode 100644 tests/ValueObject/Fixture/tests/some_file.php delete mode 100644 tests/ValueObject/Fixture/vendor/autoload.php delete mode 100644 tests/ValueObject/ProjectTest.php diff --git a/.github/workflows/bare_run.yaml b/.github/workflows/bare_run.yaml deleted file mode 100644 index c6d1775d..00000000 --- a/.github/workflows/bare_run.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: Bare Run on various PHP versions - -on: - push: - branches: - - main - -jobs: - bare_run: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - php_version: ['7.4', '8.2'] - - steps: - - - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php_version }} - coverage: none - - - run: composer require rector/argtyper - - - run: vendor/bin/argtyper list --ansi diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..42781bfb --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,25 @@ +name: Build + +on: + pull_request: null + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - run: gofmt -l . | tee /tmp/gofmt.out && test ! -s /tmp/gofmt.out + + - run: go vet ./... + + - run: go build ./... + + - run: go test ./... diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml deleted file mode 100644 index c4f9ee8c..00000000 --- a/.github/workflows/code_analysis.yaml +++ /dev/null @@ -1,62 +0,0 @@ -name: Code Analysis - -on: - pull_request: null - push: - branches: - - main - -jobs: - code_analysis: - strategy: - fail-fast: false - matrix: - actions: - - - name: 'PHPStan' - run: composer phpstan --ansi - - - - name: 'Composer Validate' - run: composer validate --ansi - - - - name: 'Rector' - run: composer rector --ansi - - - - name: 'Coding Standard' - run: composer fix-cs --ansi - - - - name: 'Tests' - run: vendor/bin/phpunit - - - - name: 'PHP Linter' - run: vendor/bin/parallel-lint src tests - - - - name: 'Dependency Analysis' - run: vendor/bin/composer-dependency-analyser - - - - name: 'Check Active Classes' - run: vendor/bin/class-leak check src bin tests --ansi --skip-path=Fixture --skip-path=Source --skip-type="Rector\\ArgTyper\\PHPStan\\IgnoreAllErrorsExceptArgTyperErrorExtension" - - name: ${{ matrix.actions.name }} - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - # see https://github.com/shivammathur/setup-php - - uses: shivammathur/setup-php@v2 - with: - php-version: 8.3 - coverage: none - - # composer install cache - https://github.com/ramsey/composer-install - - uses: "ramsey/composer-install@v2" - - - run: ${{ matrix.actions.run }} - diff --git a/.github/workflows/downgraded_release.yaml b/.github/workflows/downgraded_release.yaml deleted file mode 100644 index 3176712b..00000000 --- a/.github/workflows/downgraded_release.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Downgraded Release - -on: - push: - tags: - - '*' - -jobs: - downgrade_release: - runs-on: ubuntu-latest - - steps: - - uses: "actions/checkout@v3" - with: - token: ${{ secrets.ACCESS_TOKEN }} - - - - uses: "shivammathur/setup-php@v2" - with: - php-version: 8.3 - coverage: none - - # invoke patches - - run: composer install --ansi - - # but no dev packages - - run: composer update --no-dev --ansi - - # get rector to "rector-local" directory, to avoid downgrading itself in the /vendor - - run: mkdir rector-local - - run: composer require rector/rector --working-dir rector-local --ansi - - # downgrade to PHP 7.2 - - run: rector-local/vendor/bin/rector process config src bin vendor --config build/rector-downgrade-php-72.php --ansi - - # clear the dev files - - run: rm -rf tests ecs.php phpstan.neon phpunit.xml .gitignore .editorconfig - - # prefix and scope - - run: sh prefix-code.sh - - # copy PHP 7.2 composer + workflows - - run: cp -r build/target-repository/. . - - # clear the dev files - - run: rm -rf build prefix-code.sh scoper.php rector.php rector-local php-scoper.phar - - # setup git user - - - run: | - git config user.email "action@github.com" - git config user.name "GitHub Action" - - # publish to the same repository with a new tag - # see https://tomasvotruba.com/blog/how-to-release-php-81-and-72-package-in-the-same-repository/ - - - name: "Tag Downgraded Code" - run: | - # separate a "git add" to add untracked (new) files too - git add --all - git commit -m "release PHP 7.2 downgraded" - - # force push tag, so there is only 1 version - git tag "${GITHUB_REF#refs/tags/}" --force - git push origin "${GITHUB_REF#refs/tags/}" --force diff --git a/.github/workflows/run_on_project.yaml b/.github/workflows/run_on_project.yaml deleted file mode 100644 index b9c25ed2..00000000 --- a/.github/workflows/run_on_project.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: Run on Project - -on: - push: - branches: - - main - pull_request: null - -jobs: - run_on_project: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - php_version: ['8.4'] - - steps: - - - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php_version }} - coverage: none - - - run: composer require rector/argtyper --ansi - - # clone symfony/console project with dependencies - - run: git clone https://github.com/symfony/console.git --depth=1 - - run: composer install --working-dir console --ansi - - # ensure phpstan is available - - run: composer require --working-dir console phpstan/phpstan --ansi - - # on whole project, to be able to load vendor/autoload.php - - run: vendor/bin/argtyper add-types console --ansi diff --git a/.gitignore b/.gitignore index 4d79a352..69a61fd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ -# composer -/vendor -composer.lock - -# phpunit -.phpunit.result.cache - -# local metadata -debug.json \ No newline at end of file +# go +/argtyper +*.out diff --git a/README.md b/README.md index ad3b75c9..d48c3a50 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,29 @@ # Fill Parameter Types based on Passed Values -There are often more known types in your project than meets the eye. -This tool detects the **real** types passed into method and function calls using PHPStan. +There are often more known types in your project than meets the eye. +This tool detects the types of **literal values** passed into method, constructor and function calls, then adds them as parameter type declarations.
```php -$this->hotelOverview->makeRoomAvailable(324); -``` - -
- -Later in the code... - -```php -public function roomDetail(int $roomNumber) +final class HotelOverview { - $this->hotelOverview->makeRoomAvailable($roomNumber); -} -``` - -
- -Later in tests... + public function makeRoomAvailable($roomNumber) + { + } -```php -public function test(int $roomNumber): void -{ - $this->hotelOverview->makeRoomAvailable($roomNumber); + public function bookLobby() + { + $this->makeRoomAvailable(324); + } } ``` -✅ Three times an `int` value is passed into `makeRoomAvailable()`. +✅ An `int` value is passed into `makeRoomAvailable()`.
-Then [Rector](https://getrector.com) runs and fills in the missing type declarations: +The tool fills in the missing type declaration: ```diff final class HotelOverview @@ -51,14 +39,14 @@ Then [Rector](https://getrector.com) runs and fills in the missing type declarat
-That’s it. +That's it.
## Install ```bash -composer require rector/argtyper --dev +go install github.com/rectorphp/argtyper@latest ```
@@ -68,52 +56,46 @@ composer require rector/argtyper --dev Run it in your project directory: ```bash -vendor/bin/argtyper add-types . +argtyper add-types . ``` -
- Or on another project: ```bash -vendor/bin/argtyper add-types project +argtyper add-types /path/to/project ``` -To see more details during the process, add the `--debug` option. +It scans the `src`, `lib`, `app`, `test` and `tests` directories.
## How It Works -At first, a set of custom PHPStan rules scans your code and records the argument types passed to method calls, static calls, `new` expressions, and function calls. It stores this data in temporary `*.json` files in the following format: +It is built on [php-parser-in-go](https://github.com/rectorphp/php-parser-in-go). -```json -[ - { - "class": "HotelOverview", - "method": "makeRoomAvailable", - "position": 0, - "type": "PHPStan\\Type\\IntegerType" - } -] -``` - -
- -Then, custom Rector rules go through the codebase and fill in the known parameter types based on the collected data — but only where they’re missing. +1. It walks every call site and records the type of each **literal** argument - `int`, `float`, `string`, `bool`, `array`, `null` and `new X()` (as `object`). +2. It groups the recorded types per parameter position. +3. It adds the type to each definition that is still missing one. With a few exceptions: -* If multiple types are found → it’s skipped. -* If union or intersection types are found → it’s skipped as ambiguous. -* If a `float` parameter type is declared but only `int` arguments are passed (e.g. `30.0`) → it’s skipped to avoid losing decimal precision. +* If multiple different types are found for one parameter -> it is skipped as ambiguous. +* If a single type plus `null` is found -> a nullable type is added. +* Parameters that already have a type are left untouched. +* Magic methods (except `__construct`) are skipped. +* Methods that may override a parent or interface are skipped, unless they are private or a constructor.
-## Verify the Results +## Scope + +The tool relies only on the parsed syntax tree, not on full type inference, so it works with values it can resolve statically: + +* **Literal arguments** - `f(324)`, `f("x")`, `f([1, 2])`. Variables and expressions are skipped. +* **Statically resolvable call targets** - `new X()`, `X::method()`, `self::method()`, `$this->method()` and plain `function()` calls. Calls on other variables (`$service->method()`) are skipped, because the class cannot be known without type inference. +* **Short class names** - classes are matched by their short name, not the fully qualified name. -It’s not 100 % perfect, but in our tests it fills in about **95 %** of the data correctly and saves a huge amount of manual work. -You can fix the remaining cases manually based on PHPStan or test feedback. +This catches the easy, unambiguous cases and leaves the rest for you to fill manually based on PHPStan or test feedback.
diff --git a/bin/argtyper b/bin/argtyper deleted file mode 100755 index 6103acbf..00000000 --- a/bin/argtyper +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env php -create(); - -/** @var \Symfony\Component\Console\Application $application */ -$application = $container->make(\Symfony\Component\Console\Application::class); - -$resultCode = $application->run(); -exit($resultCode); diff --git a/bin/autoload.php b/bin/autoload.php deleted file mode 100755 index 50a2f3bd..00000000 --- a/bin/autoload.php +++ /dev/null @@ -1,16 +0,0 @@ -withDowngradeSets(php72: true) - ->withSkip([__DIR__ . '/../tests']); diff --git a/build/target-repository/.github/FUNDING.yml b/build/target-repository/.github/FUNDING.yml deleted file mode 100644 index f797866a..00000000 --- a/build/target-repository/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms -github: tomasvotruba -custom: https://www.paypal.me/rectorphp diff --git a/build/target-repository/.github/workflows/auto_closer.yaml b/build/target-repository/.github/workflows/auto_closer.yaml deleted file mode 100644 index de9fca80..00000000 --- a/build/target-repository/.github/workflows/auto_closer.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: Auto Closer PR - -on: - pull_request_target: - types: [opened] - -jobs: - run: - runs-on: ubuntu-latest - steps: - - uses: superbrothers/close-pull-request@v3 - with: - # Optional. Post a issue comment just before closing a pull request. - comment: | - Hi, thank you for your contribution. - - Unfortunately, this repository is read-only. It's a split from our main monorepo repository. - - We'd like to kindly ask you to move the contribution there - https://github.com/symplify/symplify. - - We'll check it, review it and give you feed back right way. - - Thank you. diff --git a/build/target-repository/composer.json b/build/target-repository/composer.json deleted file mode 100644 index 201b18d6..00000000 --- a/build/target-repository/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "rector/argtyper", - "description": "Analyze real method argument types, and add them as type declarations", - "license": "proprietary", - "require": { - "php": ">=7.4", - "phpstan/phpstan": "^2.0", - "rector/rector": "^2.0" - }, - "bin": [ - "bin/argtyper", - "bin/argtyper.php" - ], - "autoload": { - "psr-4": { - "Rector\\ArgTyper\\": "src" - } - } -} diff --git a/composer-dependency-analyser.php b/composer-dependency-analyser.php deleted file mode 100644 index 95296d48..00000000 --- a/composer-dependency-analyser.php +++ /dev/null @@ -1,10 +0,0 @@ -ignoreErrorsOnPackage('nikic/php-parser', [ErrorType::SHADOW_DEPENDENCY]); diff --git a/composer.json b/composer.json deleted file mode 100644 index 46127fb4..00000000 --- a/composer.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "rector/argtyper", - "description": "Analyze real method argument types, and add them as type declarations", - "license": "proprietary", - "bin": [ - "bin/argtyper" - ], - "require": { - "php": "^8.3", - "illuminate/container": "12.39.*", - "nette/utils": "^4.1", - "phpstan/phpstan": "^2.1", - "rector/rector": "^2.3", - "symfony/console": "^6.4", - "symfony/finder": "^7.4", - "symfony/process": "^7.4", - "webmozart/assert": "^1.12|^2.0" - }, - "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpecs/phpecs": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpunit/phpunit": "^11.5", - "rector/jack": "^0.5.1", - "shipmonk/composer-dependency-analyser": "^1.8", - "symplify/phpstan-extensions": "^12.0", - "tomasvotruba/class-leak": "^2.1", - "tomasvotruba/unused-public": "^2.1", - "tracy/tracy": "^2.11" - }, - "autoload": { - "psr-4": { - "Rector\\ArgTyper\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "Rector\\ArgTyper\\Tests\\": "tests" - }, - "classmap": [ - "tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture" - ], - "files": [ - "tests/PHPStan/CollectFuncCallArgTypesRule/Source/some_function.php" - ] - }, - "config": { - "sort-packages": true, - "platform-check": false, - "allow-plugins": { - "phpstan/extension-installer": true - } - }, - "replace": { - "symfony/polyfill-ctype": "*", - "symfony/polyfill-intl-normalizer": "*", - "symfony/polyfill-mbstring": "*" - }, - "scripts": { - "check-cs": "vendor/bin/ecs check --ansi", - "fix-cs": "vendor/bin/ecs check --fix --ansi", - "phpstan": "vendor/bin/phpstan analyse --ansi", - "rector": "vendor/bin/rector process --dry-run --ansi" - } -} diff --git a/config/phpstan-collecting-data.neon b/config/phpstan-collecting-data.neon deleted file mode 100644 index cf562eac..00000000 --- a/config/phpstan-collecting-data.neon +++ /dev/null @@ -1,22 +0,0 @@ -parameters: - # avoid any other rules - customRulesetUsed: true - - # avoid inline-ignores to be reported - reportUnmatchedIgnoredErrors: false - - excludePaths: - - "*/Fixture/*" - - "*/Fixtures/*" - - "*/fixtures/*" - - "*/fixture/*" - -rules: - - Rector\ArgTyper\PHPStan\Rule\CollectFuncCallArgTypesRule - - Rector\ArgTyper\PHPStan\Rule\CollectCallLikeArgTypesRule - -services: - - - class: Rector\ArgTyper\PHPStan\IgnoreAllErrorsExceptArgTyperErrorExtension - tags: - - phpstan.ignoreErrorExtension diff --git a/config/rector-argtyper.php b/config/rector-argtyper.php deleted file mode 100644 index 448af636..00000000 --- a/config/rector-argtyper.php +++ /dev/null @@ -1,25 +0,0 @@ -withSkip([ - // run only on source code - 'test', - 'tests', - 'Fixture', - 'test', - 'Tests', - ]) - ->withRules([ - AddFunctionParamTypeRector::class, - AddClassMethodParamTypeRector::class, - // AddParamIterableDocblockTypeRector::class, - ]); diff --git a/ecs.php b/ecs.php deleted file mode 100644 index 97ad402f..00000000 --- a/ecs.php +++ /dev/null @@ -1,14 +0,0 @@ -withPaths([ - __DIR__ . '/bin', - __DIR__ . '/src', - __DIR__ . '/tests', - ]) - ->withPreparedSets(symplify: true, common: true, psr12: true) - ->withSkip(['Fixture']); diff --git a/func-call-collected-data.json b/func-call-collected-data.json deleted file mode 100644 index 36388696..00000000 --- a/func-call-collected-data.json +++ /dev/null @@ -1,10 +0,0 @@ -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":0,"type":"PHPStan\\Type\\IntegerType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":1,"type":"PHPStan\\Type\\FloatType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":0,"type":"PHPStan\\Type\\IntegerType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":1,"type":"PHPStan\\Type\\FloatType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":0,"type":"PHPStan\\Type\\IntegerType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":1,"type":"PHPStan\\Type\\FloatType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":0,"type":"PHPStan\\Type\\IntegerType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":1,"type":"PHPStan\\Type\\FloatType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":0,"type":"PHPStan\\Type\\IntegerType"} -{"function":"Rector\\ArgTyper\\Tests\\PHPStan\\CollectFuncCallArgTypesRule\\Source\\someFunction","position":1,"type":"PHPStan\\Type\\FloatType"} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..ba5a6d55 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/rectorphp/argtyper + +go 1.26 + +require github.com/rectorphp/php-parser-in-go v0.1.1 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..6593ebc7 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rectorphp/php-parser-in-go v0.1.1 h1:9jMbxAtDBjubUlPFKB5lbbvpXn8Otxq9eG77Q7moqwk= +github.com/rectorphp/php-parser-in-go v0.1.1/go.mod h1:6p9QnZnLGc9uruvKuSXlVt4uSIY5Rqe6h2aj9+hEBG8= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/internal/aggregate/aggregate.go b/internal/aggregate/aggregate.go new file mode 100644 index 00000000..647bb973 --- /dev/null +++ b/internal/aggregate/aggregate.go @@ -0,0 +1,113 @@ +// Package aggregate turns collected argument records into a single resolved +// type per parameter position, applying the same skip rules as the PHP tool. +package aggregate + +import ( + "sort" + "strconv" + + "github.com/rectorphp/argtyper/internal/collect" +) + +// Resolved is the final type decision for one parameter position. +type Resolved struct { + Type string // source type keyword or "object:Short", never "null" + Nullable bool +} + +// Types holds resolved parameter types keyed for fast lookup during apply. +type Types struct { + methods map[string]Resolved // class \x00 method \x00 position + functions map[string]Resolved // function \x00 position +} + +// Method returns the resolved type for a method/constructor parameter. +func (t Types) Method(class, method string, position int) (Resolved, bool) { + resolved, ok := t.methods[methodKey(class, method, position)] + return resolved, ok +} + +// Function returns the resolved type for a function parameter. +func (t Types) Function(name string, position int) (Resolved, bool) { + resolved, ok := t.functions[functionKey(name, position)] + return resolved, ok +} + +// Resolve groups records per parameter and keeps only unambiguous ones: +// a single type, or a single type plus null (nullable). +func Resolve(records []collect.Record) Types { + methodTypes := map[string]map[string]struct{}{} + functionTypes := map[string]map[string]struct{}{} + + for _, record := range records { + if record.IsFunction { + key := functionKey(record.Name, record.Position) + addType(functionTypes, key, record.Type) + continue + } + key := methodKey(record.Class, record.Name, record.Position) + addType(methodTypes, key, record.Type) + } + + return Types{ + methods: resolveGroups(methodTypes), + functions: resolveGroups(functionTypes), + } +} + +func resolveGroups(groups map[string]map[string]struct{}) map[string]Resolved { + resolved := map[string]Resolved{} + + for key, typeSet := range groups { + types := make([]string, 0, len(typeSet)) + for typeName := range typeSet { + types = append(types, typeName) + } + sort.Strings(types) + + switch { + case len(types) == 1: + if types[0] == "null" { + continue + } + resolved[key] = Resolved{Type: types[0]} + case len(types) == 2 && contains(types, "null"): + resolved[key] = Resolved{Type: without(types, "null"), Nullable: true} + } + } + + return resolved +} + +func addType(groups map[string]map[string]struct{}, key, typeName string) { + if groups[key] == nil { + groups[key] = map[string]struct{}{} + } + groups[key][typeName] = struct{}{} +} + +func methodKey(class, method string, position int) string { + return class + "\x00" + method + "\x00" + strconv.Itoa(position) +} + +func functionKey(name string, position int) string { + return name + "\x00" + strconv.Itoa(position) +} + +func contains(values []string, needle string) bool { + for _, value := range values { + if value == needle { + return true + } + } + return false +} + +func without(values []string, needle string) string { + for _, value := range values { + if value != needle { + return value + } + } + return "" +} diff --git a/internal/aggregate/aggregate_test.go b/internal/aggregate/aggregate_test.go new file mode 100644 index 00000000..912806cb --- /dev/null +++ b/internal/aggregate/aggregate_test.go @@ -0,0 +1,77 @@ +package aggregate_test + +import ( + "testing" + + "github.com/rectorphp/argtyper/internal/aggregate" + "github.com/rectorphp/argtyper/internal/collect" +) + +func TestResolveMethod(t *testing.T) { + tests := []struct { + name string + records []collect.Record + wantType string + wantNullable bool + wantFound bool + }{ + { + name: "single type", + records: []collect.Record{{Class: "A", Name: "m", Type: "int"}}, + wantType: "int", + wantFound: true, + }, + { + name: "type plus null is nullable", + records: []collect.Record{ + {Class: "A", Name: "m", Type: "string"}, + {Class: "A", Name: "m", Type: "null"}, + }, + wantType: "string", + wantNullable: true, + wantFound: true, + }, + { + name: "two real types is ambiguous", + records: []collect.Record{ + {Class: "A", Name: "m", Type: "int"}, + {Class: "A", Name: "m", Type: "string"}, + }, + wantFound: false, + }, + { + name: "only null resolves to nothing", + records: []collect.Record{{Class: "A", Name: "m", Type: "null"}}, + wantFound: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resolved, found := aggregate.Resolve(test.records).Method("A", "m", 0) + if found != test.wantFound { + t.Fatalf("found=%v want %v", found, test.wantFound) + } + if found && (resolved.Type != test.wantType || resolved.Nullable != test.wantNullable) { + t.Errorf("got %+v want type=%s nullable=%v", resolved, test.wantType, test.wantNullable) + } + }) + } +} + +func TestResolveFunctionAndPositionIsolation(t *testing.T) { + records := []collect.Record{ + {IsFunction: true, Name: "f", Position: 0, Type: "int"}, + {IsFunction: true, Name: "f", Position: 1, Type: "string"}, + } + types := aggregate.Resolve(records) + + first, ok := types.Function("f", 0) + if !ok || first.Type != "int" { + t.Errorf("position 0: got %+v ok=%v", first, ok) + } + second, ok := types.Function("f", 1) + if !ok || second.Type != "string" { + t.Errorf("position 1: got %+v ok=%v", second, ok) + } +} diff --git a/internal/apply/apply.go b/internal/apply/apply.go new file mode 100644 index 00000000..63ba93e6 --- /dev/null +++ b/internal/apply/apply.go @@ -0,0 +1,151 @@ +// Package apply fills missing parameter types in function, method and +// constructor definitions from the resolved argument types. +package apply + +import ( + "strings" + + "github.com/rectorphp/argtyper/internal/aggregate" + "github.com/rectorphp/argtyper/internal/phpast" + "github.com/rectorphp/php-parser-in-go/pkg/ast" +) + +// Source adds parameter types to a single PHP source file. It returns the new +// source, the number of types added, and whether the file changed. On a parse +// error the original source is returned unchanged. +func Source(src []byte, types aggregate.Types) (string, int, bool) { + root, err := phpast.Parse(src) + if err != nil || root == nil { + return string(src), 0, false + } + + applier := &applier{types: types} + applier.walk(root, nil) + + if applier.added == 0 { + return string(src), 0, false + } + + return phpast.Print(root), applier.added, true +} + +type applier struct { + types aggregate.Types + added int +} + +func (a *applier) walk(node ast.Vertex, class *ast.StmtClass) { + if node == nil { + return + } + + switch typed := node.(type) { + case *ast.StmtClass: + class = typed + case *ast.StmtFunction: + a.applyFunction(typed) + case *ast.StmtClassMethod: + a.applyMethod(typed, class) + } + + for _, child := range phpast.Children(node) { + a.walk(child, class) + } +} + +func (a *applier) applyFunction(function *ast.StmtFunction) { + name := phpast.ShortName(function.Name) + for position, paramNode := range function.Params { + param, ok := paramNode.(*ast.Parameter) + if !ok || !typeable(param) { + continue + } + if resolved, ok := a.types.Function(name, position); ok { + a.setType(param, resolved) + } + } +} + +func (a *applier) applyMethod(method *ast.StmtClassMethod, class *ast.StmtClass) { + name := phpast.ShortName(method.Name) + if isMagicExceptConstructor(name) { + return + } + if !overridable(method, class) { + return + } + + className := "" + if class != nil { + className = phpast.ShortName(class.Name) + } + + for position, paramNode := range method.Params { + param, ok := paramNode.(*ast.Parameter) + if !ok || !typeable(param) { + continue + } + if resolved, ok := a.types.Method(className, name, position); ok { + a.setType(param, resolved) + } + } +} + +func (a *applier) setType(param *ast.Parameter, resolved aggregate.Resolved) { + nullable := resolved.Nullable || hasNullDefault(param) + typeText := typeText(resolved.Type) + + if nullable { + param.Type = &ast.Nullable{Expr: &ast.Identifier{Value: []byte(typeText + " ")}} + } else { + param.Type = &ast.Identifier{Value: []byte(typeText + " ")} + } + a.added++ +} + +// typeText turns a resolved type into the text written into source. The trailing +// space that separates the type from the variable is added by the caller. +func typeText(resolved string) string { + if strings.HasPrefix(resolved, "object:") { + return "\\" + strings.TrimPrefix(resolved, "object:") + } + return resolved +} + +// typeable reports whether a parameter can receive a type: none yet and not +// variadic, where positions would no longer line up. +func typeable(param *ast.Parameter) bool { + return param.Type == nil && param.VariadicTkn == nil +} + +// overridable reports whether typing this method is safe without a reflection +// based parent lookup: constructors and private methods never override, and a +// class with no parent or interface cannot override either. +func overridable(method *ast.StmtClassMethod, class *ast.StmtClass) bool { + if phpast.ShortName(method.Name) == "__construct" { + return true + } + for _, modifierNode := range method.Modifiers { + if modifier, ok := modifierNode.(*ast.Identifier); ok { + if strings.ToLower(string(modifier.Value)) == "private" { + return true + } + } + } + if class == nil { + return true + } + return class.Extends == nil && len(class.Implements) == 0 +} + +func isMagicExceptConstructor(name string) bool { + return name != "__construct" && strings.HasPrefix(name, "__") +} + +func hasNullDefault(param *ast.Parameter) bool { + constFetch, ok := param.DefaultValue.(*ast.ExprConstFetch) + if !ok { + return false + } + return strings.ToLower(phpast.ShortName(constFetch.Const)) == "null" +} diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go new file mode 100644 index 00000000..8d1f0fd9 --- /dev/null +++ b/internal/apply/apply_test.go @@ -0,0 +1,119 @@ +package apply_test + +import ( + "testing" + + "github.com/rectorphp/argtyper/internal/aggregate" + "github.com/rectorphp/argtyper/internal/apply" + "github.com/rectorphp/argtyper/internal/collect" +) + +// run collects from every source, resolves types, then applies to target. +func run(target string, sources ...string) (string, int) { + var records []collect.Record + for _, source := range sources { + records = append(records, collect.FromSource([]byte(source))...) + } + types := aggregate.Resolve(records) + output, count, _ := apply.Source([]byte(target), types) + return output, count +} + +func TestApply(t *testing.T) { + tests := []struct { + name string + target string + callers []string + want string + }{ + { + name: "adds int to method from this call", + target: "room(5); }\n}", + want: "room(5); }\n}", + }, + { + name: "adds string to function", + target: "set(1); $this->set(null); }\n}", + want: "set(1); $this->set(null); }\n}", + }, + { + name: "nullable from null default keeps question mark", + target: "set(\"x\"); }\n}", + want: "set(\"x\"); }\n}", + }, + { + name: "keeps existing type untouched", + target: "set(1); }\n}", + want: "set(1); }\n}", + }, + { + name: "skips ambiguous multiple types", + target: "set(1); $this->set(\"x\"); }\n}", + want: "set(1); $this->set(\"x\"); }\n}", + }, + { + name: "skips magic method", + target: "__get(\"x\"); }\n}", + want: "__get(\"x\"); }\n}", + }, + { + name: "skips override candidate in class with interface", + target: "set(1); }\n}", + want: "set(1); }\n}", + }, + { + name: "types private method even with parent", + target: "set(1); }\n}", + want: "set(1); }\n}", + }, + { + name: "adds array type", + target: "` and `self::` calls resolve. +func (c *collector) walk(node ast.Vertex, class string) { + if node == nil { + return + } + + switch typed := node.(type) { + case *ast.StmtClass: + class = phpast.ShortName(typed.Name) + case *ast.StmtTrait: + class = phpast.ShortName(typed.Name) + case *ast.StmtEnum: + class = phpast.ShortName(typed.Name) + } + + c.visit(node, class) + + for _, child := range phpast.Children(node) { + c.walk(child, class) + } +} + +func (c *collector) visit(node ast.Vertex, class string) { + switch typed := node.(type) { + case *ast.ExprFunctionCall: + name := phpast.ShortName(typed.Function) + if name == "" { + return + } + c.record(typed.Args, Record{IsFunction: true, Name: name}) + + case *ast.ExprNew: + name := phpast.ShortName(typed.Class) + if name == "" { + return + } + c.record(typed.Args, Record{Class: name, Name: "__construct"}) + + case *ast.ExprStaticCall: + method, ok := typed.Call.(*ast.Identifier) + if !ok { + return + } + target := staticClassName(typed.Class, class) + if target == "" { + return + } + c.record(typed.Args, Record{Class: target, Name: string(method.Value)}) + + case *ast.ExprMethodCall: + if !phpast.IsThisVariable(typed.Var) || class == "" { + return + } + method, ok := typed.Method.(*ast.Identifier) + if !ok { + return + } + c.record(typed.Args, Record{Class: class, Name: string(method.Value)}) + + case *ast.ExprNullsafeMethodCall: + if !phpast.IsThisVariable(typed.Var) || class == "" { + return + } + method, ok := typed.Method.(*ast.Identifier) + if !ok { + return + } + c.record(typed.Args, Record{Class: class, Name: string(method.Value)}) + } +} + +// staticClassName resolves the class of a static call: self/static map to the +// enclosing class, parent is unresolvable, anything else is a plain name. +func staticClassName(classNode ast.Vertex, enclosing string) string { + name := phpast.ShortName(classNode) + switch strings.ToLower(name) { + case "self", "static": + return enclosing + case "parent": + return "" + default: + return name + } +} + +func (c *collector) record(args []ast.Vertex, base Record) { + for position, argNode := range args { + arg, ok := argNode.(*ast.Argument) + if !ok { + continue + } + // skip named and variadic arguments, positions no longer line up + if arg.Name != nil || arg.VariadicTkn != nil { + continue + } + + typeName := literalType(arg.Expr) + if typeName == "" { + continue + } + + record := base + record.Position = position + record.Type = typeName + c.records = append(c.records, record) + } +} + +// literalType returns the type of a literal argument expression, or "" when +// the value is not a literal we can infer without a type engine. +func literalType(expr ast.Vertex) string { + switch typed := expr.(type) { + case *ast.ScalarLnumber: + return "int" + case *ast.ScalarDnumber: + return "float" + case *ast.ScalarString, *ast.ScalarEncapsed, *ast.ScalarHeredoc: + return "string" + case *ast.ExprArray: + return "array" + case *ast.ExprUnaryMinus: + return numericType(typed.Expr) + case *ast.ExprUnaryPlus: + return numericType(typed.Expr) + case *ast.ExprConstFetch: + switch strings.ToLower(phpast.ShortName(typed.Const)) { + case "true", "false": + return "bool" + case "null": + return "null" + } + return "" + case *ast.ExprNew: + name := phpast.ShortName(typed.Class) + if name == "" { + return "" + } + return "object:" + name + default: + return "" + } +} + +func numericType(expr ast.Vertex) string { + switch expr.(type) { + case *ast.ScalarLnumber: + return "int" + case *ast.ScalarDnumber: + return "float" + default: + return "" + } +} diff --git a/internal/collect/collect_test.go b/internal/collect/collect_test.go new file mode 100644 index 00000000..f3831231 --- /dev/null +++ b/internal/collect/collect_test.go @@ -0,0 +1,97 @@ +package collect_test + +import ( + "testing" + + "github.com/rectorphp/argtyper/internal/collect" +) + +func TestFromSource(t *testing.T) { + tests := []struct { + name string + src string + want []collect.Record + }{ + { + name: "this method call int", + src: "set(5); }\n}", + want: []collect.Record{{Class: "A", Name: "set", Position: 0, Type: "int"}}, + }, + { + name: "function call string", + src: "set(1); }\n}", + want: nil, + }, + { + name: "skips named argument", + src: " 1 { + projectPath = args[1] + } + + files, err := finder.PHPFiles(projectPath) + if err != nil { + return err + } + + fmt.Printf("Code dirs found in %q: %v\n\n", projectPath, finder.CodeDirectories(projectPath)) + + // 1. collect literal argument types across the whole project + fmt.Println("1. Collecting argument types...") + var records []collect.Record + for _, file := range files { + src, err := os.ReadFile(file) + if err != nil { + return err + } + records = append(records, collect.FromSource(src)...) + } + fmt.Printf(" Found %d arg types\n\n", len(records)) + + types := aggregate.Resolve(records) + + // 2. add the resolved types to parameter declarations + fmt.Println("2. Adding types to parameters...") + added := 0 + for _, file := range files { + src, err := os.ReadFile(file) + if err != nil { + return err + } + + output, count, changed := apply.Source(src, types) + if !changed { + continue + } + + if err := os.WriteFile(file, []byte(output), 0o644); err != nil { + return err + } + added += count + } + + if added == 0 { + fmt.Println(" No new types added. Is your code that good?") + return nil + } + + fmt.Printf(" Finished! Added %d new types\n", added) + return nil +} diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index 46df4a7c..00000000 --- a/phpstan.neon +++ /dev/null @@ -1,33 +0,0 @@ -parameters: - paths: - - bin - - src - - tests - - rector.php - - ecs.php - - level: 8 - errorFormat: symplify - - reportUnmatchedIgnoredErrors: false - - # make sure iterable docblock are checked with real assertions - treatPhpDocTypesAsCertain: false - - ignoreErrors: - # irelevant - - '#Calling PHPStan\\(.*?) is not covered by backward compatibility promise\. The method might change in a minor PHPStan version#' - - identifier: phpstanApi.constructor - - - identifier: phpstanApi.instanceofType - - # false positive - - '#with generic interface PHPStan\\Collectors\\Collector#' - - - - identifier: class.notFound - path: tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/AddClassMethodParamTypeRectorTest.php - - excludePaths: - - "*/Fixture/*" - - "*/Source/*" diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 7d4b4ab4..00000000 --- a/phpunit.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - tests - tests/PHPStan/Fixture - - - diff --git a/prefix-code.sh b/prefix-code.sh deleted file mode 100644 index 910bca81..00000000 --- a/prefix-code.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash - -# inspired from https://github.com/rectorphp/rector/blob/main/build/build-rector-scoped.sh - -# see https://stackoverflow.com/questions/66644233/how-to-propagate-colors-from-bash-script-to-github-action?noredirect=1#comment117811853_66644233 -export TERM=xterm-color - -# show errors -set -e - -# script fails if trying to access to an undefined variable -set -u - - -# functions -note() -{ - MESSAGE=$1; - printf "\n"; - echo "\033[0;33m[NOTE] $MESSAGE\033[0m"; -} - -# --------------------------- - -# 2. scope it -note "Downloading php-scoper" -wget https://github.com/humbug/php-scoper/releases/download/0.18.18/php-scoper.phar -N --no-verbose - - -note "Running php-scoper" - -# Work around possible PHP memory limits -php -d memory_limit=-1 php-scoper.phar add-prefix config src bin vendor composer.json --config scoper.php --force --ansi --output-dir scoped-code - -# the output code is in "/scoped-code", lets move it up -# the local directories have to be empty to move easily -rm -r config src bin vendor composer.json -mv scoped-code/* . - -note "Dumping Composer Autoload" -composer dump-autoload --ansi --classmap-authoritative --no-dev - -# make bin runnable without "php" -chmod 777 "bin/argtyper" -chmod 777 "bin/argtyper.php" - -note "Finished" diff --git a/rector.php b/rector.php deleted file mode 100644 index 91c32eed..00000000 --- a/rector.php +++ /dev/null @@ -1,29 +0,0 @@ -withPaths([ - __DIR__ . '/bin', - __DIR__ . '/src', - __DIR__ . '/tests', - ]) - ->withRootFiles() - ->withPhpSets() - ->withPreparedSets( - deadCode: true, - codeQuality: true, - codingStyle: true, - typeDeclarations: true, - typeDeclarationDocblocks: true, - naming: true, - instanceOf: true, - earlyReturn: true, - phpunitCodeQuality: true, - ) - ->withSkip(['*/Source', '*/Fixture', - \Rector\CodingStyle\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector::class => [ - __DIR__ . '/src/Command/ArgTyperCommand.php', - ]]); diff --git a/scoper.php b/scoper.php deleted file mode 100644 index 7cc0582f..00000000 --- a/scoper.php +++ /dev/null @@ -1,20 +0,0 @@ -format('Ym'); - -// see https://github.com/humbug/php-scoper -return [ - 'prefix' => 'Argtyper' . $timestamp, - 'expose-constants' => ['#^SYMFONY\_[\p{L}_]+$#'], - 'exclude-namespaces' => ['#^Rector#', '#^PhpParser#', '#^PHPStan#', '#^Symfony\\\\Polyfill#'], - 'exclude-files' => [ - // do not prefix "trigger_deprecation" from symfony - https://github.com/symfony/symfony/commit/0032b2a2893d3be592d4312b7b098fb9d71aca03 - // these paths are relative to this file location, so it should be in the root directory - 'vendor/symfony/deprecation-contracts/function.php', - ], -]; diff --git a/src/Command/AddTypesCommand.php b/src/Command/AddTypesCommand.php deleted file mode 100644 index 221caad3..00000000 --- a/src/Command/AddTypesCommand.php +++ /dev/null @@ -1,137 +0,0 @@ -setName('add-types'); - $this->setDescription( - 'Find all passed values and their types to your local methods/functions calls, then add them as type declarations' - ); - - $this->addArgument('project-path', InputArgument::OPTIONAL, 'Path to the target project root', getcwd()); - - $this->addOption('debug', null, null, 'Enable debug output'); - } - - /** - * @return Command::* - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - $projectPath = (string) $input->getArgument('project-path'); - - $project = new Project($projectPath); - $isDebug = (bool) $input->getOption('debug'); - - $this->symfonyStyle->writeln(sprintf('Code dirs found in the "%s" project', $projectPath)); - $this->symfonyStyle->listing($project->getCodeDirectories()); - $this->symfonyStyle->newLine(2); - - // 1. Run PHPStan data collection - $this->runPhpStan($project, $isDebug); - - $this->symfonyStyle->newLine(); - - // 2. Run Rector to apply types, not on tests, just source - // Discover source dirs - $this->runRector($project, $isDebug); - - $this->removeTemporaryPHPStanJsonFiles(); - - return Command::SUCCESS; - } - - private function runPhpStan(Project $project, bool $isDebug): void - { - $this->symfonyStyle->title('1. Running PHPStan to collect data...'); - - // Keep paths the same as in the original script - $commands = [ - 'vendor/bin/phpstan', - 'analyse', - ...$project->getCodeDirectories(), - '--configuration', - (string) realpath(__DIR__ . '/../../config/phpstan-collecting-data.neon'), - '--autoload-file', - (string) realpath(__DIR__ . '/../../bin/autoload.php'), - ]; - - $this->processRunner->runProcess($commands, $project->getDirectory(), $isDebug); - - $collectedFileItems = FilesLoader::loadJsonl(ConfigFilePath::callLikes()); - $this->symfonyStyle->success(sprintf('Finished! Found %d arg types', count($collectedFileItems))); - } - - private function runRector(Project $project, bool $isDebug): void - { - $this->symfonyStyle->title('2. Running Rector to add types...'); - - $command = [ - 'vendor/bin/rector', - 'process', - ...$project->getAbsoluteCodeDirectories(), - '--config', - (string) realpath(__DIR__ . '/../../config/rector-argtyper.php'), - '--clear-cache', - ]; - - // show output, so we know what exactly has changed - // we have to use getcwd() as Rector is only available in project here - $rectorOutput = $this->processRunner->runProcess($command, getcwd(), $isDebug); - - $addedTypesCount = $this->resolveAddedTypesCount($rectorOutput); - - if ($addedTypesCount === 0) { - $this->symfonyStyle->writeln('No new types added. Is your code that good?'); - $this->symfonyStyle->newLine(); - return; - } - - $this->symfonyStyle->success(sprintf('Finished! We have added %d new types', $addedTypesCount)); - } - - private function resolveAddedTypesCount(string $rectorOutput): int - { - // regex: match lines that start with + but not +++ or @@ - $pattern = '/^(?:\+)(?!\+\+|@@).+/m'; - - if (preg_match_all($pattern, $rectorOutput, $matches)) { - return count($matches[0]); - } - - return 0; - } - - private function removeTemporaryPHPStanJsonFiles(): void - { - if (file_exists(ConfigFilePath::funcCalls())) { - unlink(ConfigFilePath::funcCalls()); - } - - if (file_exists(ConfigFilePath::callLikes())) { - unlink(ConfigFilePath::callLikes()); - } - } -} diff --git a/src/Configuration/CallLikeTypesConfigurationProvider.php b/src/Configuration/CallLikeTypesConfigurationProvider.php deleted file mode 100644 index 90de24e4..00000000 --- a/src/Configuration/CallLikeTypesConfigurationProvider.php +++ /dev/null @@ -1,151 +0,0 @@ - - */ - private array $classMethodTypes = []; - - /** - * @return array - */ - public function matchByPosition(ClassMethod $classMethod): array - { - $scope = ScopeFetcher::fetch($classMethod); - - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return []; - } - - if ($classReflection->isAnonymous()) { - return []; - } - - $classMethodTypes = $this->provide(); - - $className = $classReflection->getName(); - $methodName = $classMethod->name->toString(); - - $matchingClassMethodTypes = $this->matchByClassAndMethodNames($classMethodTypes, $className, $methodName); - Assert::allIsInstanceOf($matchingClassMethodTypes, ClassMethodType::class); - - $classMethodTypesByPosition = []; - foreach ($matchingClassMethodTypes as $matchingClassMethodType) { - $classMethodTypesByPosition[$matchingClassMethodType->getPosition()][] = $matchingClassMethodType; - } - - return $classMethodTypesByPosition; - } - - /** - * @api used only in tests - * @param ClassMethodType[] $classMethodTypes - */ - public function seedClassMethodTypes(array $classMethodTypes): void - { - Assert::allIsInstanceOf($classMethodTypes, ClassMethodType::class); - - $this->classMethodTypes = $classMethodTypes; - } - - /** - * @return array - */ - private function provide(): array - { - if ($this->classMethodTypes !== []) { - return $this->classMethodTypes; - } - - $phpstanResultsData = FilesLoader::loadJsonl(ConfigFilePath::callLikes()); - - $dataGroupedByPositionMethodAndClassNames = []; - - foreach ($phpstanResultsData as $phpstanResultData) { - $dataGroupedByPositionMethodAndClassNames[$phpstanResultData['class']][$phpstanResultData['method']][$phpstanResultData['position']][] = $phpstanResultData['type']; - } - - $classMethodTypes = []; - - foreach ($dataGroupedByPositionMethodAndClassNames as $className => $typesByPositionByMethodNames) { - foreach ($typesByPositionByMethodNames as $methodName => $typesByPosition) { - foreach ($typesByPosition as $position => $types) { - $uniqueTypes = array_unique($types); - $uniqueTypes = array_values($uniqueTypes); - sort($uniqueTypes); - - if (count($uniqueTypes) === 1) { - // easy path, pick sole type - $classMethodTypes[] = new ClassMethodType( - $className, - $methodName, - $position, - $uniqueTypes[0] - ); - - continue; - } - - if (in_array(NullType::class, $uniqueTypes) && count($uniqueTypes) === 2) { - $typesWithoutNull = array_diff($uniqueTypes, [NullType::class]); - $typesWithoutNull = array_values($typesWithoutNull); - - $typeWithoutNull = $typesWithoutNull[0]; - - $classMethodTypes[] = new ClassMethodType( - $className, - $methodName, - $position, - $typeWithoutNull, - true - ); - - continue; - } - - // log invalid type to improve - FilesLoader::writeJsonl('debug.json', [ - 'skipped_types' => $uniqueTypes, - ]); - } - } - } - - $this->classMethodTypes = $classMethodTypes; - - return $classMethodTypes; - } - - /** - * @param array $classMethodTypes - * @return array - */ - private function matchByClassAndMethodNames(array $classMethodTypes, string $className, string $methodName): array - { - return array_filter( - $classMethodTypes, - function (ClassMethodType $classMethodType) use ($className, $methodName): bool { - if ($classMethodType->getClass() !== $className) { - return false; - } - - return $classMethodType->getMethod() === $methodName; - } - ); - } -} diff --git a/src/Configuration/FuncCallTypesConfigurationProvider.php b/src/Configuration/FuncCallTypesConfigurationProvider.php deleted file mode 100644 index 89652eb7..00000000 --- a/src/Configuration/FuncCallTypesConfigurationProvider.php +++ /dev/null @@ -1,110 +0,0 @@ - - */ - private array $funcCallTypes = []; - - /** - * @return array - */ - public function matchByPosition(Function_ $function): array - { - if (! $function->namespacedName instanceof Name) { - return []; - } - - $functionName = $function->namespacedName->toString(); - - $functionTypes = $this->provide(); - - $matchingFunctionTypes = array_filter( - $functionTypes, - fn (FuncCallType $funcCallType): bool => $funcCallType->getFunction() === $functionName - ); - - Assert::allIsInstanceOf($matchingFunctionTypes, FuncCallType::class); - - $typesByPosition = []; - - foreach ($matchingFunctionTypes as $matchingFunctionType) { - $typesByPosition[$matchingFunctionType->getPosition()][] = $matchingFunctionType; - } - - return $typesByPosition; - } - - /** - * @param array $funcCallTypes - * @api used only in tests - */ - public function seedTypes(array $funcCallTypes): void - { - Assert::allIsInstanceOf($funcCallTypes, FuncCallType::class); - $this->funcCallTypes = $funcCallTypes; - } - - /** - * @return array - */ - private function provide(): array - { - if ($this->funcCallTypes !== []) { - return $this->funcCallTypes; - } - - $phpstanResultsData = FilesLoader::loadJsonl(ConfigFilePath::funcCalls()); - - $dataGroupedByPositionFunctionName = []; - - foreach ($phpstanResultsData as $phpstanResultData) { - $dataGroupedByPositionFunctionName[$phpstanResultData['function']][$phpstanResultData['position']][] = $phpstanResultData['type']; - } - - $funcCallTypes = []; - - foreach ($dataGroupedByPositionFunctionName as $functionName => $typesByPosition) { - foreach ($typesByPosition as $position => $types) { - $uniqueTypes = array_unique($types); - - if (count($uniqueTypes) === 1) { - // easy path, pick sole type - $funcCallTypes[] = new FuncCallType($functionName, $position, $uniqueTypes[0]); - continue; - } - - if (in_array(NullType::class, $uniqueTypes) && count($uniqueTypes) === 2) { - $typesWithoutNull = array_diff($uniqueTypes, [NullType::class]); - - $typeWithoutNull = $typesWithoutNull[0]; - - $funcCallTypes[] = new FuncCallType($functionName, $position, $typeWithoutNull, true); - continue; - } - - // log invalid type to improve - FilesLoader::writeJsonl(getcwd() . '/debug.json', [ - 'skipped_types' => $uniqueTypes, - ]); - } - } - - $this->funcCallTypes = $funcCallTypes; - - return $funcCallTypes; - } -} diff --git a/src/DependencyInjection/ContainerFactory.php b/src/DependencyInjection/ContainerFactory.php deleted file mode 100644 index f75a7a06..00000000 --- a/src/DependencyInjection/ContainerFactory.php +++ /dev/null @@ -1,68 +0,0 @@ -singleton(Parser::class, static function (): Parser { - $parserFactory = new ParserFactory(); - return $parserFactory->createForHostVersion(); - }); - - $container->singleton( - SymfonyStyle::class, - static function (): SymfonyStyle { - // use null output ofr tests to avoid printing - $consoleOutput = defined('PHPUNIT_COMPOSER_INSTALL') ? new NullOutput() : new ConsoleOutput(); - return new SymfonyStyle(new ArrayInput([]), $consoleOutput); - } - ); - - $container->singleton(Application::class, function (Container $container): Application { - /** @var AddTypesCommand $addTypesCommand */ - $addTypesCommand = $container->make(AddTypesCommand::class); - - $application = new Application(); - $application->add($addTypesCommand); - - $this->hideDefaultCommands($application); - - return $application; - }); - - return $container; - } - - /** - * @see https://tomasvotruba.com/blog/how-make-your-tool-commands-list-easy-to-read - */ - private function hideDefaultCommands(Application $application): void - { - $application->get('completion') - ->setHidden(); - $application->get('help') - ->setHidden(); - } -} diff --git a/src/Enum/ConfigFilePath.php b/src/Enum/ConfigFilePath.php deleted file mode 100644 index 238e60c6..00000000 --- a/src/Enum/ConfigFilePath.php +++ /dev/null @@ -1,18 +0,0 @@ - $record - */ - public static function writeJsonl(string $filePath, array $record): void - { - // ensure file exists - if (! file_exists($filePath)) { - touch($filePath); - } - - // newline is important for JSONL format - $line = Json::encode($record) . PHP_EOL; - - // Append the line and lock the file to prevent race conditions - file_put_contents($filePath, $line, FILE_APPEND | LOCK_EX); - } - - /** - * @return array> - */ - public static function loadJsonl(string $filePath): array - { - // ensure file exists - if (! file_exists($filePath)) { - touch($filePath); - } - - $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - Assert::isArray($lines); - - $records = []; - foreach ($lines as $line) { - $records[] = Json::decode($line, true); - } - - return $records; - } -} diff --git a/src/Helpers/ProjectDirectoryFinder.php b/src/Helpers/ProjectDirectoryFinder.php deleted file mode 100644 index 5171737e..00000000 --- a/src/Helpers/ProjectDirectoryFinder.php +++ /dev/null @@ -1,66 +0,0 @@ -findCodeDirsAbsolute($projectPath) as $absoluteDir) { - $relativeDirs[] = substr($absoluteDir, strlen((string) realpath($projectPath)) + 1); - } - - return $relativeDirs; - } - - /** - * @return string[] - */ - public function findCodeDirsAbsolute(string $projectPath): array - { - $fileInfos = $this->findDirectoriesInPaths($projectPath, self::POSSIBLE_CODE_DIRECTORIES); - - $absoluteDirs = []; - foreach ($fileInfos as $fileInfo) { - $absoluteDirs[] = $fileInfo->getRealPath(); - } - - return $absoluteDirs; - } - - /** - * @param string[] $desiredDirectoryNames - * @return SplFileInfo[] - */ - private function findDirectoriesInPaths(string $projectPath, array $desiredDirectoryNames): array - { - Assert::allString($desiredDirectoryNames); - - $finder = (new Finder()) - ->in($projectPath) - ->directories() - ->depth('== 0') - ->name($desiredDirectoryNames) - ->sortByName(); - - /** @var SplFileInfo[] $fileInfos */ - $fileInfos = iterator_to_array($finder->getIterator()); - return $fileInfos; - } -} diff --git a/src/Helpers/ReflectionChecker.php b/src/Helpers/ReflectionChecker.php deleted file mode 100644 index 1470a5e2..00000000 --- a/src/Helpers/ReflectionChecker.php +++ /dev/null @@ -1,46 +0,0 @@ -isInternal()) { - return true; - } - - if (self::isVendor($classReflection)) { - return true; - } - - return ! $classReflection->hasMethod($methodName); - } - - public static function shouldSkipFunctionReflection(FunctionReflection $functionReflection): bool - { - if ($functionReflection->isInternal()->yes()) { - return true; - } - - return self::isVendor($functionReflection); - } - - private static function isVendor(ClassReflection|FunctionReflection $reflection): bool - { - $fileName = $reflection->getFileName(); - - // most likely internal or magic - if ($fileName === null) { - return true; - } - - // is part of vendor? we can't change that, so skip it - return str_contains($fileName, '/vendor'); - } -} diff --git a/src/PHPStan/CallLikeClassReflectionResolver.php b/src/PHPStan/CallLikeClassReflectionResolver.php deleted file mode 100644 index 2cdc6a45..00000000 --- a/src/PHPStan/CallLikeClassReflectionResolver.php +++ /dev/null @@ -1,63 +0,0 @@ -resolveNewAndStaticCall($callLike); - } - - $methodCallerType = $scope->getType($callLike->var); - - // @todo check if this can be less strict, e.g. for nullable etc. - if (! $methodCallerType->isObject()->yes()) { - return null; - } - - // unwrap "self::" and "$this" calls - if ($methodCallerType instanceof StaticType) { - $methodCallerType = $methodCallerType->getStaticObjectType(); - } - - if ($methodCallerType instanceof ObjectType) { - return $methodCallerType->getClassReflection(); - } - - return null; - } - - private function resolveNewAndStaticCall(New_|StaticCall $callLike): ?ClassReflection - { - if (! $callLike->class instanceof Name) { - return null; - } - - $className = $callLike->class->toString(); - if (! $this->reflectionProvider->hasClass($className)) { - return null; - } - - return $this->reflectionProvider->getClass($className); - } -} diff --git a/src/PHPStan/IgnoreAllErrorsExceptArgTyperErrorExtension.php b/src/PHPStan/IgnoreAllErrorsExceptArgTyperErrorExtension.php deleted file mode 100644 index dc12a032..00000000 --- a/src/PHPStan/IgnoreAllErrorsExceptArgTyperErrorExtension.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * @see \Rector\ArgTyper\Tests\PHPStan\CollectCallLikeArgTypesRule\CollectCallLikeArgTypesRuleTest - */ -final readonly class CollectCallLikeArgTypesRule implements Rule -{ - private TypeMapper $typeMapper; - - private CallLikeClassReflectionResolver $callLikeClassReflectionResolver; - - public function __construct(ReflectionProvider $reflectionProvider) - { - $this->typeMapper = new TypeMapper(); - $this->callLikeClassReflectionResolver = new CallLikeClassReflectionResolver($reflectionProvider); - } - - /** - * @return class-string - */ - public function getNodeType(): string - { - return CallLike::class; - } - - /** - * @param MethodCall|FuncCall|StaticCall|NullsafeMethodCall $node - */ - public function processNode(Node $node, Scope $scope): array - { - // nothing to find here - if ($node->isFirstClassCallable() || $node->getArgs() === []) { - return []; - } - - if ($node instanceof FuncCall) { - return []; - } - - // 1. - if ($node instanceof New_) { - $methodName = '__construct'; - } elseif ($node->name instanceof Identifier) { - $methodName = $node->name->toString(); - } else { - return []; - } - - $classReflection = $this->callLikeClassReflectionResolver->resolve($node, $scope); - - // nothing to find here - if (! $classReflection instanceof ClassReflection) { - return []; - } - - if (ReflectionChecker::shouldSkipClassReflection($classReflection, $methodName)) { - return []; - } - - foreach ($node->getArgs() as $key => $arg) { - $typeString = $this->typeMapper->mapToStringIfUseful($arg, $scope); - if (! is_string($typeString)) { - continue; - } - - FilesLoader::writeJsonl( - ConfigFilePath::callLikes(), - [ - 'class' => $classReflection->getName(), - 'method' => $methodName, - 'position' => $key, - 'type' => $typeString, - ] - ); - } - - // comply with contract, but never used - return []; - } -} diff --git a/src/PHPStan/Rule/CollectFuncCallArgTypesRule.php b/src/PHPStan/Rule/CollectFuncCallArgTypesRule.php deleted file mode 100644 index 4d5f61e5..00000000 --- a/src/PHPStan/Rule/CollectFuncCallArgTypesRule.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * @see \Rector\ArgTyper\Tests\PHPStan\CollectFuncCallArgTypesRule\CollectFuncCallArgTypesRuleTest - */ -final readonly class CollectFuncCallArgTypesRule implements Rule -{ - private TypeMapper $typeMapper; - - public function __construct( - private ReflectionProvider $reflectionProvider - ) { - $this->typeMapper = new TypeMapper(); - } - - public function getNodeType(): string - { - return FuncCall::class; - } - - /** - * @param FuncCall $node - */ - public function processNode(Node $node, Scope $scope): array - { - // nothing to find here - if ($node->isFirstClassCallable() || $node->getArgs() === []) { - return []; - } - - if (! $node->name instanceof Name) { - return []; - } - - if (! $this->reflectionProvider->hasFunction($node->name, $scope)) { - return []; - } - - $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); - if (ReflectionChecker::shouldSkipFunctionReflection($functionReflection)) { - return []; - } - - foreach ($node->getArgs() as $key => $arg) { - $typeString = $this->typeMapper->mapToStringIfUseful($arg, $scope); - if (! is_string($typeString)) { - continue; - } - - FilesLoader::writeJsonl( - ConfigFilePath::funcCalls(), - [ - 'function' => $functionReflection->getName(), - 'position' => $key, - 'type' => $typeString, - ] - ); - } - - // nothing to return, just comply with contract - return []; - } -} diff --git a/src/PHPStan/TypeMapper.php b/src/PHPStan/TypeMapper.php deleted file mode 100644 index 909fb083..00000000 --- a/src/PHPStan/TypeMapper.php +++ /dev/null @@ -1,98 +0,0 @@ -name instanceof Identifier) { - return null; - } - - $type = $scope->getType($arg->value); - if ($this->shouldSkipType($type)) { - return null; - } - - if ($type instanceof TypeWithClassName) { - return 'object:' . $type->getClassName(); - } - - $genericType = $this->mapConstantToGenericTypes($type); - return $genericType::class; - } - - private function mapConstantToGenericTypes(Type $type): Type - { - // correct to generic types - if ($type instanceof IntegerRangeType) { - return new IntegerType(); - } - - if ($type instanceof ClassStringType) { - return new StringType(); - } - - // allow adding "array" type in case of passing multiple array and constant array types - if ($type instanceof ConstantArrayType) { - return new ArrayType(new MixedType(), new MixedType()); - } - - if ($type instanceof ArrayType) { - return $type; - } - - if ($type instanceof ConstantStringType) { - return new StringType(); - } - - if ($type instanceof ConstantIntegerType) { - return new IntegerType(); - } - - if ($type instanceof ConstantFloatType) { - return new FloatType(); - } - - if ($type instanceof ConstantBooleanType) { - return new BooleanType(); - } - - return $type; - } - - private function shouldSkipType(Type $type): bool - { - // unable to move to json for now, handle later - if ($type instanceof MixedType) { - return true; - } - - return $type instanceof UnionType || $type instanceof IntersectionType; - } -} diff --git a/src/Process/ProcessRunner.php b/src/Process/ProcessRunner.php deleted file mode 100644 index 1505badd..00000000 --- a/src/Process/ProcessRunner.php +++ /dev/null @@ -1,36 +0,0 @@ -setTimeout(null); - - if ($isDebug) { - $this->symfonyStyle->writeln(sprintf('$ %s', $process->getCommandLine())); - $this->symfonyStyle->newLine(); - } - - $process->mustRun(); - return $process->getOutput(); - } -} diff --git a/src/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector.php b/src/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector.php deleted file mode 100644 index 28a79e90..00000000 --- a/src/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector.php +++ /dev/null @@ -1,181 +0,0 @@ -getMethods() as $classMethod) { - if ($this->shouldSkipClassMethod($classMethod)) { - continue; - } - - $classMethodTypesByPosition = $this->callLikeTypesConfigurationProvider->matchByPosition($classMethod); - if ($classMethodTypesByPosition === []) { - continue; - } - - if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod)) { - continue; - } - - foreach ($classMethod->getParams() as $position => $param) { - // skip as already has complex type - if ($param->type instanceof UnionType) { - continue; - } - - if ($param->type instanceof IntersectionType) { - continue; - } - - $paramClassMethodTypes = $classMethodTypesByPosition[$position] ?? null; - if ($paramClassMethodTypes === null) { - continue; - } - - $classMethodType = $paramClassMethodTypes[0]; - - // nothing useful in type declarations - if (in_array( - $classMethodType->getType(), - [NullType::class, ResourceType::class, NeverType::class], - true - )) { - continue; - } - - // a null default value implies the type must stay nullable - $isNullable = $classMethodType->isNullable() - || $param->type instanceof NullableType - || $this->hasNullDefault($param); - - $typeNode = TypeResolver::resolveTypeNode($classMethodType->getType()); - - if ($this->shouldSkipOverride($param, $classMethodType)) { - continue; - } - - // already has the exact scalar type and nullability, nothing to change - if ($typeNode instanceof Identifier && $this->hasSameScalarType($param, $typeNode, $isNullable)) { - continue; - } - - if ($classMethodType->isObjectType() && ($param->type instanceof Name || ($param->type instanceof NullableType && $param->type->type instanceof Name))) { - // skip already set object type - continue; - } - - if ($isNullable) { - $param->type = new NullableType($typeNode); - $hasChanged = true; - } else { - $param->type = $typeNode; - $hasChanged = true; - } - } - } - - if (! $hasChanged) { - return null; - } - - return $node; - } - - private function shouldSkipClassMethod(ClassMethod $classMethod): bool - { - // empty params - if ($classMethod->getParams() === []) { - return true; - } - - if ($classMethod->name->toString() === MethodName::CONSTRUCT) { - return false; - } - - return $classMethod->isMagic(); - } - - private function hasSameScalarType(Param $param, Identifier $identifier, bool $isNullable): bool - { - if (($param->type instanceof NullableType) !== $isNullable) { - return false; - } - - $rawType = $param->type instanceof NullableType ? $param->type->type : $param->type; - if (! $rawType instanceof Identifier) { - return false; - } - - return $rawType->toString() === $identifier->toString(); - } - - private function hasNullDefault(Param $param): bool - { - if (! $param->default instanceof ConstFetch) { - return false; - } - - return $param->default->name->toLowerString() === 'null'; - } - - private function shouldSkipOverride(Param $param, ClassMethodType $classMethodType): bool - { - $rawType = $param->type instanceof NullableType ? $param->type->type : $param->type; - - // just to be safe - if ($rawType instanceof Identifier && in_array($rawType->toString(), ['iterable', 'float'], true)) { - return true; - } - - // skip already set object type - return $classMethodType->isObjectType() && $rawType instanceof Name; - } -} diff --git a/src/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector.php b/src/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector.php deleted file mode 100644 index 2899c2ac..00000000 --- a/src/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector.php +++ /dev/null @@ -1,127 +0,0 @@ -getMethods() as $classMethod) { - if ($classMethod->isMagic()) { - continue; - } - - if ($classMethod->getParams() === []) { - continue; - } - - $classMethodTypesByPosition = $this->callLikeTypesConfigurationProvider->matchByPosition($classMethod); - if ($classMethodTypesByPosition === []) { - continue; - } - - foreach ($classMethod->getParams() as $position => $param) { - // only look for array types - if (! $this->isParamTypeArray($param)) { - continue; - } - - $paramClassMethodTypes = $classMethodTypesByPosition[$position] ?? null; - if ($paramClassMethodTypes === null) { - continue; - } - - $classMethodType = $paramClassMethodTypes[0]; - - $classMethodPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); - - /** @var string $paramName */ - $paramName = $this->getName($param->var); - - // already known - if ($classMethodPhpDocInfo->getParamType($paramName) instanceof ParamTagValueNode) { - continue; - } - - if (! $this->isUsefulArrayType($classMethodType)) { - continue; - } - - $typeNode = $this->docStringTypeMapper->mapToTypeNode($classMethodType->getType()); - if (! $typeNode instanceof TypeNode) { - continue; - } - - $paramTagValueNode = new ParamTagValueNode($typeNode, false, '$' . $paramName, '', false); - $classMethodPhpDocInfo->addTagValueNode($paramTagValueNode); - - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classMethod); - $hasChanged = true; - - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classMethod); - } - } - - if (! $hasChanged) { - return null; - } - - return $node; - } - - private function isParamTypeArray(Param $param): bool - { - if (! $param->type instanceof Node) { - return false; - } - - return $this->isName($param->type, 'array'); - } - - private function isUsefulArrayType(ClassMethodType $classMethodType): bool - { - if (! str_starts_with($classMethodType->getType(), 'array')) { - return false; - } - - // not detailed much - return $classMethodType->getType() !== 'array'; - } -} diff --git a/src/Rector/Rector/Function_/AddFunctionParamTypeRector.php b/src/Rector/Rector/Function_/AddFunctionParamTypeRector.php deleted file mode 100644 index b58277e5..00000000 --- a/src/Rector/Rector/Function_/AddFunctionParamTypeRector.php +++ /dev/null @@ -1,102 +0,0 @@ -getParams() === []) { - return null; - } - - $hasChanged = false; - - foreach ($node->getParams() as $position => $param) { - $functionTypesByPosition = $this->funcCallTypesConfigurationProvider->matchByPosition($node); - if ($functionTypesByPosition === []) { - continue; - } - - $paramFunctionTypes = $functionTypesByPosition[$position] ?? null; - if ($paramFunctionTypes === null) { - continue; - } - - if (count($paramFunctionTypes) >= 2) { - throw new NotImplementedException('Multiple types not implemented yet'); - } - - $paramFunctionType = $paramFunctionTypes[0]; - - // nothing useful - if (in_array($paramFunctionType->getType(), [NullType::class, ResourceType::class, NeverType::class])) { - continue; - } - - // a null default value implies the type must stay nullable - $isNullable = $paramFunctionType->isNullable() || $this->hasDefaultNull($param); - $typeNode = TypeResolver::resolveTypeNode($paramFunctionType->getType()); - - if ($paramFunctionType->isObjectType() && ($param->type instanceof Name || ($param->type instanceof NullableType && $param->type->type instanceof Name))) { - // skip already set object type - continue; - } - - if ($isNullable) { - $param->type = new NullableType($typeNode); - $hasChanged = true; - } else { - $param->type = $typeNode; - $hasChanged = true; - } - } - - if (! $hasChanged) { - return null; - } - - return $node; - } - - private function hasDefaultNull(Param $param): bool - { - if (! $param->default instanceof ConstFetch) { - return false; - } - - return $param->default->name->toLowerString() === 'null'; - } -} diff --git a/src/Rector/TypeMapper/DocStringTypeMapper.php b/src/Rector/TypeMapper/DocStringTypeMapper.php deleted file mode 100644 index 1ee3c29e..00000000 --- a/src/Rector/TypeMapper/DocStringTypeMapper.php +++ /dev/null @@ -1,38 +0,0 @@ -tokenize('@param ' . $typeString . '$someParam'); - - $constExprParser = new ConstExprParser($parserConfig); - $typeParser = new TypeParser($parserConfig, $constExprParser); - - $phpDocParser = new PhpDocParser($parserConfig, $typeParser, $constExprParser); - - $phpDocTagNode = $phpDocParser->parseTag(new TokenIterator($tokens)); - if (! $phpDocTagNode->value instanceof ParamTagValueNode) { - return null; - } - - return $phpDocTagNode->value->type; - } -} diff --git a/src/Rector/TypeResolver.php b/src/Rector/TypeResolver.php deleted file mode 100644 index f68713b6..00000000 --- a/src/Rector/TypeResolver.php +++ /dev/null @@ -1,51 +0,0 @@ -position; - } - - public function getClass(): string - { - return $this->class; - } - - public function getMethod(): string - { - return $this->method; - } - - public function getType(): string - { - return $this->type; - } - - public function isObjectType(): bool - { - return str_starts_with($this->type, 'object:'); - } - - public function isNullable(): bool - { - return $this->isNullable; - } -} diff --git a/src/Rector/ValueObject/FuncCallType.php b/src/Rector/ValueObject/FuncCallType.php deleted file mode 100644 index fd78dd95..00000000 --- a/src/Rector/ValueObject/FuncCallType.php +++ /dev/null @@ -1,41 +0,0 @@ -function; - } - - public function getPosition(): int - { - return $this->position; - } - - public function getType(): string - { - return $this->type; - } - - public function isObjectType(): bool - { - return str_starts_with($this->type, 'object:'); - } - - public function isNullable(): bool - { - return $this->isNullable; - } -} diff --git a/src/ValueObject/Project.php b/src/ValueObject/Project.php deleted file mode 100644 index 54400327..00000000 --- a/src/ValueObject/Project.php +++ /dev/null @@ -1,50 +0,0 @@ -findCodeDirsRelative($this->directory); - } - - /** - * @return string[] - */ - public function getAbsoluteCodeDirectories(): array - { - $projectDirectoryFinder = new ProjectDirectoryFinder(); - return $projectDirectoryFinder->findCodeDirsAbsolute($this->directory); - } - - public function getDirectory(): string - { - return $this->directory; - } -} diff --git a/tests/PHPStan/CollectCallLikeArgTypesRule/CollectCallLikeArgTypesRuleTest.php b/tests/PHPStan/CollectCallLikeArgTypesRule/CollectCallLikeArgTypesRuleTest.php deleted file mode 100644 index e134b47c..00000000 --- a/tests/PHPStan/CollectCallLikeArgTypesRule/CollectCallLikeArgTypesRuleTest.php +++ /dev/null @@ -1,128 +0,0 @@ - - */ -final class CollectCallLikeArgTypesRuleTest extends RuleTestCase -{ - protected function setUp(): void - { - parent::setUp(); - - // cleanup collected data file - @unlink(ConfigFilePath::callLikes()); - } - - public function testMissingParent(): void - { - $collectedTypes = $this->collectDataInFile(__DIR__ . '/Fixture/AllowMissingParentType.php'); - - $this->assertCount(1, $collectedTypes); - } - - public function testMethodCallAndStaticCall(): void - { - $collectedTypes = $this->collectDataInFile(__DIR__ . '/Fixture/MethodCalledArgs.php'); - $this->assertCount(2, $collectedTypes); - - $this->assertSame([ - 'class' => SomeObject::class, - 'method' => 'setName', - 'position' => 0, - 'type' => StringType::class, - ], $collectedTypes[0]); - - $this->assertSame([ - 'class' => SomeObject::class, - 'method' => 'setAge', - 'position' => 0, - 'type' => IntegerType::class, - ], $collectedTypes[1]); - } - - public function testConstructor(): void - { - $collectedType = $this->collectDataInFile(__DIR__ . '/Fixture/ConstructorArgs.php'); - - $this->assertSame([ - 'class' => ObjectWithConstructor::class, - 'method' => '__construct', - 'position' => 0, - 'type' => IntegerType::class, - ], $collectedType[0]); - - $this->assertSame([ - 'class' => ObjectWithConstructor::class, - 'method' => '__construct', - 'position' => 0, - 'type' => IntegerType::class, - ], $collectedType[1]); - - $this->assertSame([ - 'class' => ObjectWithConstructor::class, - 'method' => '__construct', - 'position' => 0, - 'type' => StringType::class, - ], $collectedType[2]); - } - - public function testFloatAsInt(): void - { - $collectedType = $this->collectDataInFile(__DIR__ . '/Fixture/FloatAsInt.php'); - - $this->assertSame([ - 'class' => ObjectWithConstructor::class, - 'method' => '__construct', - 'position' => 0, - 'type' => FloatType::class, - ], $collectedType[0]); - - $this->assertSame([ - 'class' => ObjectWithConstructor::class, - 'method' => '__construct', - 'position' => 0, - 'type' => FloatType::class, - ], $collectedType[1]); - } - - /** - * @return string[] - */ - #[\Override] - public static function getAdditionalConfigFiles(): array - { - return [__DIR__ . '/../../../config/phpstan-collecting-data.neon']; - } - - protected function getRule(): Rule - { - return self::getContainer()->getByType(CollectCallLikeArgTypesRule::class); - } - - /** - * @return array> - */ - private function collectDataInFile(string $fixtureFilePath): array - { - Assert::fileExists($fixtureFilePath); - $this->analyse([$fixtureFilePath], []); - - return FilesLoader::loadJsonl(ConfigFilePath::callLikes()); - } -} diff --git a/tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/AllowMissingParentType.php b/tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/AllowMissingParentType.php deleted file mode 100644 index 95217baf..00000000 --- a/tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/AllowMissingParentType.php +++ /dev/null @@ -1,22 +0,0 @@ -run(100); - - $classWithMissingParentType->parentCall(100); - } - - public function runAgain(MissingClass $missingClass) - { - $missingClass->run(200); - } -} diff --git a/tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/ConstructorArgs.php b/tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/ConstructorArgs.php deleted file mode 100644 index fec94eec..00000000 --- a/tests/PHPStan/CollectCallLikeArgTypesRule/Fixture/ConstructorArgs.php +++ /dev/null @@ -1,19 +0,0 @@ -setName('some name'); - } - - public function go(): void - { - SomeObject::setAge(100); - } -} diff --git a/tests/PHPStan/CollectCallLikeArgTypesRule/Source/ClassWithMissingParentType.php b/tests/PHPStan/CollectCallLikeArgTypesRule/Source/ClassWithMissingParentType.php deleted file mode 100644 index 29a9ebaf..00000000 --- a/tests/PHPStan/CollectCallLikeArgTypesRule/Source/ClassWithMissingParentType.php +++ /dev/null @@ -1,12 +0,0 @@ -name = $name; - } - - public static function setAge($age): void - { - self::$age = $age; - } -} diff --git a/tests/PHPStan/CollectFuncCallArgTypesRule/CollectFuncCallArgTypesRuleTest.php b/tests/PHPStan/CollectFuncCallArgTypesRule/CollectFuncCallArgTypesRuleTest.php deleted file mode 100644 index 32f4f39f..00000000 --- a/tests/PHPStan/CollectFuncCallArgTypesRule/CollectFuncCallArgTypesRuleTest.php +++ /dev/null @@ -1,62 +0,0 @@ - - */ -final class CollectFuncCallArgTypesRuleTest extends RuleTestCase -{ - public function test(): void - { - $collectedTypes = $this->collectDataInFile(__DIR__ . '/Fixture/SimpleFunctionCall.php'); - - $this->assertSame([ - 'function' => 'Rector\ArgTyper\Tests\PHPStan\CollectFuncCallArgTypesRule\Source\someFunction', - 'position' => 0, - 'type' => IntegerType::class, - ], $collectedTypes[0]); - - $this->assertSame([ - 'function' => 'Rector\ArgTyper\Tests\PHPStan\CollectFuncCallArgTypesRule\Source\someFunction', - 'position' => 1, - 'type' => FloatType::class, - ], $collectedTypes[1]); - } - - /** - * @return string[] - */ - #[\Override] - public static function getAdditionalConfigFiles(): array - { - return [__DIR__ . '/../../../config/phpstan-collecting-data.neon']; - } - - protected function getRule(): Rule - { - return self::getContainer()->getByType(CollectFuncCallArgTypesRule::class); - } - - /** - * @return array> - */ - private function collectDataInFile(string $fixtureFilePath): array - { - Assert::fileExists($fixtureFilePath); - $this->analyse([$fixtureFilePath], []); - - return FilesLoader::loadJsonl(ConfigFilePath::funcCalls()); - } -} diff --git a/tests/PHPStan/CollectFuncCallArgTypesRule/Fixture/SimpleFunctionCall.php b/tests/PHPStan/CollectFuncCallArgTypesRule/Fixture/SimpleFunctionCall.php deleted file mode 100644 index c223e31a..00000000 --- a/tests/PHPStan/CollectFuncCallArgTypesRule/Fixture/SimpleFunctionCall.php +++ /dev/null @@ -1,15 +0,0 @@ -getContainer() - ->get(CallLikeTypesConfigurationProvider::class); - - $classMethodTypes = [ - new ClassMethodType(SkipParentContract::class, 'checkItem', 0, IntegerType::class), - new ClassMethodType(KeepNullableDateTimeInterface::class, 'record', 0, 'object:' . \DateTime::class), - new ClassMethodType(KeepDateTimeInterface::class, 'record', 0, 'object:' . \DateTime::class), - new ClassMethodType(SkipIntToFloatOverride::class, 'passInteger', 0, IntegerType::class), - new ClassMethodType(AddNullableForDefaultNull::class, 'run', 0, StringType::class), - new ClassMethodType(KeepNullableScalarParam::class, 'translate', 0, StringType::class), - new ClassMethodType(AddNullableScalarFromNullDefault::class, 'translate', 0, StringType::class), - ]; - $callLikeTypesConfigurationProvider->seedClassMethodTypes($classMethodTypes); - - $this->doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_for_default_null.php.inc b/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_for_default_null.php.inc deleted file mode 100644 index e6a45dec..00000000 --- a/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_for_default_null.php.inc +++ /dev/null @@ -1,25 +0,0 @@ - ------ - diff --git a/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_scalar_from_null_default.php.inc b/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_scalar_from_null_default.php.inc deleted file mode 100644 index 691c5351..00000000 --- a/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/add_nullable_scalar_from_null_default.php.inc +++ /dev/null @@ -1,25 +0,0 @@ - ------ - diff --git a/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/keep_date_time_interface.php.inc b/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/keep_date_time_interface.php.inc deleted file mode 100644 index 4e5e0e84..00000000 --- a/tests/Rector/Rector/ClassMethod/AddClassMethodParamTypeRector/Fixture/keep_date_time_interface.php.inc +++ /dev/null @@ -1,10 +0,0 @@ -withRules([AddClassMethodParamTypeRector::class]); diff --git a/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/AddParamIterableDocblockTypeRectorTest.php b/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/AddParamIterableDocblockTypeRectorTest.php deleted file mode 100644 index 9ed63de8..00000000 --- a/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/AddParamIterableDocblockTypeRectorTest.php +++ /dev/null @@ -1,43 +0,0 @@ -getContainer() - ->get(CallLikeTypesConfigurationProvider::class); - - $classMethodTypes = [ - new ClassMethodType( - 'Rector\ArgTyper\Tests\Rector\Rector\ClassMethod\AddParamIterableDocblockTypeRector\Fixture\SomeClass', - 'run', - 0, - 'array' - ), - ]; - $callLikeTypesConfigurationProvider->seedClassMethodTypes($classMethodTypes); - - $this->doTestFile($filePath); - } - - public static function provideData(): \Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/Fixture/some_fixture.php.inc b/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/Fixture/some_fixture.php.inc deleted file mode 100644 index af4ec693..00000000 --- a/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/Fixture/some_fixture.php.inc +++ /dev/null @@ -1,28 +0,0 @@ - ------ - $items - */ - public function run(array $items) - { - } -} - -?> diff --git a/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/config/configured_rule.php b/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/config/configured_rule.php deleted file mode 100644 index 41230837..00000000 --- a/tests/Rector/Rector/ClassMethod/AddParamIterableDocblockTypeRector/config/configured_rule.php +++ /dev/null @@ -1,9 +0,0 @@ -withRules([AddParamIterableDocblockTypeRector::class]); diff --git a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/AddFunctionParamTypeRectorTest.php b/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/AddFunctionParamTypeRectorTest.php deleted file mode 100644 index 7d57af80..00000000 --- a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/AddFunctionParamTypeRectorTest.php +++ /dev/null @@ -1,50 +0,0 @@ -getContainer() - ->get(FuncCallTypesConfigurationProvider::class); - - $funcCallTypesConfigurationProvider->seedTypes([ - new FuncCallType( - 'Rector\ArgTyper\Tests\Rector\Rector\Function_\AddFunctionParamTypeRector\Fixture\simpleFunction', - 0, - StringType::class - ), - new FuncCallType( - 'Rector\ArgTyper\Tests\Rector\Rector\Function_\AddFunctionParamTypeRector\Fixture\defaultNullFunction', - 0, - StringType::class - ), - ]); - - $this->doTestFile($filePath); - } - - public static function provideData(): \Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule.php'; - } -} diff --git a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/default_null_function.php.inc b/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/default_null_function.php.inc deleted file mode 100644 index 05f711cb..00000000 --- a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/default_null_function.php.inc +++ /dev/null @@ -1,19 +0,0 @@ - ------ - diff --git a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/simple_function.php.inc b/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/simple_function.php.inc deleted file mode 100644 index f045cac7..00000000 --- a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/Fixture/simple_function.php.inc +++ /dev/null @@ -1,19 +0,0 @@ - ------ - diff --git a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/config/configured_rule.php b/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/config/configured_rule.php deleted file mode 100644 index 324e6ef4..00000000 --- a/tests/Rector/Rector/Function_/AddFunctionParamTypeRector/config/configured_rule.php +++ /dev/null @@ -1,9 +0,0 @@ -withRules([AddFunctionParamTypeRector::class]); diff --git a/tests/ValueObject/Fixture/src/some_file.php b/tests/ValueObject/Fixture/src/some_file.php deleted file mode 100644 index b3d9bbc7..00000000 --- a/tests/ValueObject/Fixture/src/some_file.php +++ /dev/null @@ -1 +0,0 @@ -assertSame(__DIR__ . '/Fixture', $project->getDirectory()); - - $this->assertSame(['src', 'tests'], $project->getCodeDirectories()); - $this->assertSame( - [__DIR__ . '/Fixture/src', __DIR__ . '/Fixture/tests'], - $project->getAbsoluteCodeDirectories() - ); - } - - public function testMessage(): void - { - $this->expectExceptionMessage('The path "non-existing-path" is not a directory'); - - new Project('non-existing-path'); - } - - public function testMissingAutoload(): void - { - $this->expectExceptionMessage( - 'Could not find "vendor/autoload.php" in the project. Make sure its dependencies are installed' - ); - - new Project(__DIR__); - } -}