diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ee242a8 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,28 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer at sebastian@phpunit.de. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/3/0/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f68dbc7..bb816f5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -7,7 +7,10 @@ We look forward to your contributions! Here are some examples how you can contri * [Report a bug](https://github.com/sebastianbergmann/php-file-iterator/issues/new) * [Send a pull request to fix a bug](https://github.com/sebastianbergmann/php-file-iterator/pulls) -Please do not send pull requests that expand the scope of this project (see below). + +## We have a Code of Conduct + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. ## Any contributions you make will be under the BSD-3-Clause License @@ -15,6 +18,24 @@ Please do not send pull requests that expand the scope of this project (see belo When you submit code changes, your submissions are understood to be under the same [BSD-3-Clause License](https://github.com/sebastianbergmann/php-file-iterator/blob/main/LICENSE) that covers the project. By contributing to this project, you agree that your contributions will be licensed under its BSD-3-Clause License. +### Do Not Violate Copyright + +Only submit a pull request with your own original code. Do NOT submit a pull request containing code which you have largely copied from +another project, unless you wrote the respective code yourself. + +Open Source does not mean that copyright does not apply. Copyright infringements will not be tolerated and can lead to you being banned from this project and repository. + + +### Do Not Submit AI-Generated Pull Requests + +The same goes for (largely) AI-generated pull requests. These are not welcome as they will be based on copyrighted code from others +without accreditation and without taking the license of the original code into account, let alone getting permission +for the use of the code or for re-licensing. + +Aside from that, the experience is that AI-generated pull requests will be incorrect 100% of the time and cost reviewers too much time. +Submitting a (largely) AI-generated pull request will lead to you being banned from this project and repository. + + ## Write bug reports with detail, background, and sample code [This is an example](https://github.com/sebastianbergmann/phpunit/issues/4376) of a bug report I wrote, and I think it's not too bad. @@ -29,13 +50,17 @@ In your bug report, please provide the following: * What actually happens * Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) +Please do not report a bug for a version of this library that is no longer supported. Please do not report a bug if you are using a version of PHP that is not supported by the version of this library you are using. + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. Support for this library follows the [support for the version of PHPUnit that uses a specific version of this library](https://phpunit.de/supported-versions.html). + Please post code and output as text ([using proper markup](https://guides.github.com/features/mastering-markdown/)). Do not post screenshots of code or output. ## Workflow for Pull Requests 1. Fork the repository. -2. Create your branch from the oldest branch that is affected by the bug you plan to fix. +2. Create your branch from `main` if you plan to implement new functionality or change existing code significantly; create your branch from the oldest branch that is affected by the bug if you plan to fix a bug. 3. Implement your change and add tests for it. 4. Ensure the test suite passes. 5. Ensure the code complies with our coding guidelines (see below). @@ -45,6 +70,12 @@ Please make sure you have [set up your username and email address](https://git-s We encourage you to [sign your Git commits with your GPG key](https://docs.github.com/en/github/authenticating-to-github/signing-commits). +Pull requests for bug fixes must be made for the oldest branch that is supported (see above). Pull requests for new features must be based on the `main` branch. + +We are trying to keep backwards compatibility breaks to an absolute minimum. Please take this into account when proposing changes. + +Due to time constraints, we are not always able to respond as quickly as we would like. Please do not take delays personal and feel free to remind us if you feel that we forgot to respond. + ## Development diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c2fba0f..b43b838 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,4 @@ github: sebastianbergmann +liberapay: sebastianbergmann +thanks_dev: u/gh/sebastianbergmann +tidelift: "packagist/phpunit/php-file-iterator" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e7b768..b1c28ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,13 @@ # https://help.github.com/en/categories/automating-your-workflow-with-github-actions on: - - "pull_request" - - "push" + - pull_request + - push -name: "CI" +name: CI env: - COMPOSER_ROOT_VERSION: "5.1.x-dev" + COMPOSER_ROOT_VERSION: 6.0.x-dev permissions: contents: read @@ -61,34 +61,37 @@ jobs: fail-fast: false matrix: php-version: - - "8.2" - - "8.3" - - "8.4" - - "8.5" + - 8.3 + - 8.4 + - 8.5 steps: - - name: "Checkout" - uses: "actions/checkout@v4" + - name: Checkout + uses: actions/checkout@v4 - - name: "Install PHP with extensions" - uses: "shivammathur/setup-php@v2" + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 with: - php-version: "${{ matrix.php-version }}" - coverage: "xdebug" + php-version: ${{ matrix.php-version }} + coverage: xdebug - - name: "Install dependencies with Composer" - run: "./tools/composer update --no-ansi --no-interaction --no-progress" + - name: Install dependencies with Composer + run: ./tools/composer update --no-ansi --no-interaction --no-progress - - name: "Run tests with PHPUnit" - run: "vendor/bin/phpunit --log-junit junit.xml --coverage-clover=coverage.xml" + - name: Run tests with PHPUnit + run: ./vendor/bin/phpunit --log-junit test-results.xml --coverage-clover=code-coverage.xml - name: Upload test results to Codecov.io if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true + files: ./test-results.xml - name: Upload code coverage data to Codecov.io uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true + files: ./code-coverage.xml diff --git a/.phive/phars.xml b/.phive/phars.xml index 79047d2..ce852a9 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -2,5 +2,4 @@ - diff --git a/ChangeLog.md b/ChangeLog.md index bc3d870..a0fa134 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [6.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + ## [5.1.0] - 2024-08-27 ### Added @@ -168,6 +174,7 @@ No changes * [#23](https://github.com/sebastianbergmann/php-file-iterator/pull/23): Added support for wildcards (glob) in exclude +[6.0.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/5.1...6.0.0 [5.1.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/5.0.1...5.1.0 [5.0.1]: https://github.com/sebastianbergmann/php-file-iterator/compare/5.0.0...5.0.1 [5.0.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/4.1...5.0.0 diff --git a/build.xml b/build.xml index e6a7804..5277044 100644 --- a/build.xml +++ b/build.xml @@ -17,10 +17,27 @@ - + + + + + + - + + + + + + + + + + + + + diff --git a/composer.json b/composer.json index 8653c22..528f298 100644 --- a/composer.json +++ b/composer.json @@ -21,17 +21,17 @@ }, "config": { "platform": { - "php": "8.2.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "prefer-stable": true, "require": { - "php": ">=8.2" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } } } diff --git a/phpstan.neon b/phpstan.neon index e9a9e7e..94486a0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,4 +2,46 @@ parameters: level: 10 paths: - src - - tests + - tests/unit + + checkTooWideReturnTypesInProtectedAndPublicMethods: true + reportAlwaysTrueInLastCondition: true + reportPossiblyNonexistentConstantArrayOffset: true + reportPossiblyNonexistentGeneralArrayOffset: true + treatPhpDocTypesAsCertain: false + + strictRules: + allRules: false + booleansInConditions: true + closureUsesThis: true + disallowedBacktick: true + disallowedEmpty: true + disallowedImplicitArrayCreation: true + disallowedLooseComparison: true + disallowedShortTernary: true + illegalConstructorMethodCall: true + matchingInheritedMethodNames: true + noVariableVariables: true + numericOperandsInArithmeticOperators: true + overwriteVariablesWithLoop: true + requireParentConstructorCall: true + strictFunctionCalls: true + switchConditionsMatchingType: true + uselessCast: true + + ergebnis: + allRules: false + final: + enabled: true + privateInFinalClass: + enabled: true + + type_coverage: + declare: 100 + return: 100 + param: 100 + property: 100 + constant: 100 + +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/phpunit.xml b/phpunit.xml index 95cb949..de2c11b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,10 +1,8 @@ globstar($path)) { + $locals = $this->globstar($path); + + if ($locals !== []) { $_paths[] = array_map('\realpath', $locals); } else { // @codeCoverageIgnoreStart diff --git a/src/Iterator.php b/src/Iterator.php index a1c9408..19e8dd3 100644 --- a/src/Iterator.php +++ b/src/Iterator.php @@ -24,8 +24,8 @@ */ final class Iterator extends FilterIterator { - public const PREFIX = 0; - public const SUFFIX = 1; + public const int PREFIX = 0; + public const int SUFFIX = 1; private false|string $basePath; /** @@ -72,7 +72,7 @@ public function accept(): bool private function acceptPath(string $path): bool { // Filter files in hidden directories by checking path that is relative to the base path. - if (preg_match('=/\.[^/]*/=', str_replace((string) $this->basePath, '', $path))) { + if (preg_match('=/\.[^/]*/=', str_replace((string) $this->basePath, '', $path)) === 1) { return false; } @@ -94,7 +94,7 @@ private function acceptSuffix(string $filename): bool */ private function acceptSubString(string $filename, array $subStrings, int $type): bool { - if (empty($subStrings)) { + if ($subStrings === []) { return true; } diff --git a/tools/.phpstan/composer.json b/tools/.phpstan/composer.json new file mode 100644 index 0000000..8ef20ff --- /dev/null +++ b/tools/.phpstan/composer.json @@ -0,0 +1,14 @@ +{ + "require-dev": { + "phpstan/phpstan": "^2.1.8", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-strict-rules": "^2.0.3", + "tomasvotruba/type-coverage": "^2.0.2", + "ergebnis/phpstan-rules": "^2.8.0" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/tools/.phpstan/composer.lock b/tools/.phpstan/composer.lock new file mode 100644 index 0000000..6c4d892 --- /dev/null +++ b/tools/.phpstan/composer.lock @@ -0,0 +1,388 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7c032feec49c9dbbdaa0d52198cabb4c", + "packages": [], + "packages-dev": [ + { + "name": "ergebnis/phpstan-rules", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/phpstan-rules.git", + "reference": "30e790621fbad05573ef9cd355279fff5122e080" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/30e790621fbad05573ef9cd355279fff5122e080", + "reference": "30e790621fbad05573ef9cd355279fff5122e080", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "phpstan/phpstan": "^2.0.0" + }, + "require-dev": { + "doctrine/orm": "^2.20.0 || ^3.3.0", + "ergebnis/composer-normalize": "^2.45.0", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.43.0", + "ergebnis/phpunit-slow-test-detector": "^2.18.0", + "nette/di": "^3.1.10", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "phpunit/phpunit": "^9.6.21", + "psr/container": "^2.0.2", + "symfony/finder": "^5.4.45", + "symfony/process": "^5.4.47" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides rules for phpstan/phpstan.", + "homepage": "https://github.com/ergebnis/phpstan-rules", + "keywords": [ + "PHPStan", + "phpstan-rules" + ], + "support": { + "issues": "https://github.com/ergebnis/phpstan-rules/issues", + "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/phpstan-rules" + }, + "time": "2025-02-18T11:20:05+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.4" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.5", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.5" + }, + "time": "2024-08-07T15:39:19+00:00" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-03-09T09:30:48+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba", + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3" + }, + "time": "2025-01-21T10:52:14+00:00" + }, + { + "name": "tomasvotruba/type-coverage", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/type-coverage.git", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2 || ^4.0", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Measure type coverage of your project", + "keywords": [ + "phpstan-extension", + "static analysis" + ], + "support": { + "issues": "https://github.com/TomasVotruba/type-coverage/issues", + "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-01-07T00:10:26+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/tools/.phpstan/vendor/autoload.php b/tools/.phpstan/vendor/autoload.php new file mode 100644 index 0000000..87b8902 --- /dev/null +++ b/tools/.phpstan/vendor/autoload.php @@ -0,0 +1,25 @@ +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan'); + } +} + +return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan'; diff --git a/tools/.phpstan/vendor/bin/phpstan.phar b/tools/.phpstan/vendor/bin/phpstan.phar new file mode 100755 index 0000000..fecf96f --- /dev/null +++ b/tools/.phpstan/vendor/bin/phpstan.phar @@ -0,0 +1,119 @@ +#!/usr/bin/env php +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar'); + } +} + +return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar'; diff --git a/tools/.phpstan/vendor/composer/ClassLoader.php b/tools/.phpstan/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..7824d8f --- /dev/null +++ b/tools/.phpstan/vendor/composer/ClassLoader.php @@ -0,0 +1,579 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/tools/.phpstan/vendor/composer/InstalledVersions.php b/tools/.phpstan/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..6d29bff --- /dev/null +++ b/tools/.phpstan/vendor/composer/InstalledVersions.php @@ -0,0 +1,378 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = strtr(__DIR__, '\\', '/'); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/tools/.phpstan/vendor/composer/LICENSE b/tools/.phpstan/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/tools/.phpstan/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/tools/.phpstan/vendor/composer/autoload_classmap.php b/tools/.phpstan/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..feb7a8f --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_classmap.php @@ -0,0 +1,59 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Nette\\ArgumentOutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\DeprecatedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\DirectoryNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\FileNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\HtmlStringable' => $vendorDir . '/nette/utils/src/HtmlStringable.php', + 'Nette\\IOException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidArgumentException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidStateException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => $vendorDir . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => $vendorDir . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => $vendorDir . '/nette/utils/src/compatibility.php', + 'Nette\\Localization\\Translator' => $vendorDir . '/nette/utils/src/Translator.php', + 'Nette\\MemberAccessException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\NotImplementedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\NotSupportedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\OutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\SmartObject' => $vendorDir . '/nette/utils/src/SmartObject.php', + 'Nette\\StaticClass' => $vendorDir . '/nette/utils/src/StaticClass.php', + 'Nette\\UnexpectedValueException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\Utils\\ArrayHash' => $vendorDir . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => $vendorDir . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => $vendorDir . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileInfo' => $vendorDir . '/nette/utils/src/Utils/FileInfo.php', + 'Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Finder' => $vendorDir . '/nette/utils/src/Utils/Finder.php', + 'Nette\\Utils\\Floats' => $vendorDir . '/nette/utils/src/Utils/Floats.php', + 'Nette\\Utils\\Helpers' => $vendorDir . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/compatibility.php', + 'Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageColor' => $vendorDir . '/nette/utils/src/Utils/ImageColor.php', + 'Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ImageType' => $vendorDir . '/nette/utils/src/Utils/ImageType.php', + 'Nette\\Utils\\Iterables' => $vendorDir . '/nette/utils/src/Utils/Iterables.php', + 'Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\ReflectionMethod' => $vendorDir . '/nette/utils/src/Utils/ReflectionMethod.php', + 'Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\Type' => $vendorDir . '/nette/utils/src/Utils/Type.php', + 'Nette\\Utils\\UnknownImageFileException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => $vendorDir . '/nette/utils/src/Utils/Validators.php', +); diff --git a/tools/.phpstan/vendor/composer/autoload_files.php b/tools/.phpstan/vendor/composer/autoload_files.php new file mode 100644 index 0000000..b62e293 --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/phpstan/phpstan/bootstrap.php', +); diff --git a/tools/.phpstan/vendor/composer/autoload_namespaces.php b/tools/.phpstan/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/tomasvotruba/type-coverage/src'), + 'PHPStan\\ExtensionInstaller\\' => array($vendorDir . '/phpstan/extension-installer/src'), + 'PHPStan\\' => array($vendorDir . '/phpstan/phpstan-strict-rules/src'), + 'Ergebnis\\PHPStan\\Rules\\' => array($vendorDir . '/ergebnis/phpstan-rules/src'), +); diff --git a/tools/.phpstan/vendor/composer/autoload_real.php b/tools/.phpstan/vendor/composer/autoload_real.php new file mode 100644 index 0000000..c91f94e --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_real.php @@ -0,0 +1,48 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/tools/.phpstan/vendor/composer/autoload_static.php b/tools/.phpstan/vendor/composer/autoload_static.php new file mode 100644 index 0000000..041214c --- /dev/null +++ b/tools/.phpstan/vendor/composer/autoload_static.php @@ -0,0 +1,110 @@ + __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'T' => + array ( + 'TomasVotruba\\TypeCoverage\\' => 26, + ), + 'P' => + array ( + 'PHPStan\\ExtensionInstaller\\' => 27, + 'PHPStan\\' => 8, + ), + 'E' => + array ( + 'Ergebnis\\PHPStan\\Rules\\' => 23, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'TomasVotruba\\TypeCoverage\\' => + array ( + 0 => __DIR__ . '/..' . '/tomasvotruba/type-coverage/src', + ), + 'PHPStan\\ExtensionInstaller\\' => + array ( + 0 => __DIR__ . '/..' . '/phpstan/extension-installer/src', + ), + 'PHPStan\\' => + array ( + 0 => __DIR__ . '/..' . '/phpstan/phpstan-strict-rules/src', + ), + 'Ergebnis\\PHPStan\\Rules\\' => + array ( + 0 => __DIR__ . '/..' . '/ergebnis/phpstan-rules/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Nette\\ArgumentOutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\DeprecatedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\DirectoryNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\FileNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\HtmlStringable' => __DIR__ . '/..' . '/nette/utils/src/HtmlStringable.php', + 'Nette\\IOException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidArgumentException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidStateException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => __DIR__ . '/..' . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => __DIR__ . '/..' . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php', + 'Nette\\Localization\\Translator' => __DIR__ . '/..' . '/nette/utils/src/Translator.php', + 'Nette\\MemberAccessException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\NotImplementedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\NotSupportedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\OutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\SmartObject' => __DIR__ . '/..' . '/nette/utils/src/SmartObject.php', + 'Nette\\StaticClass' => __DIR__ . '/..' . '/nette/utils/src/StaticClass.php', + 'Nette\\UnexpectedValueException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\Utils\\ArrayHash' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => __DIR__ . '/..' . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileInfo' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileInfo.php', + 'Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Finder' => __DIR__ . '/..' . '/nette/utils/src/Utils/Finder.php', + 'Nette\\Utils\\Floats' => __DIR__ . '/..' . '/nette/utils/src/Utils/Floats.php', + 'Nette\\Utils\\Helpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php', + 'Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageColor' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageColor.php', + 'Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ImageType' => __DIR__ . '/..' . '/nette/utils/src/Utils/ImageType.php', + 'Nette\\Utils\\Iterables' => __DIR__ . '/..' . '/nette/utils/src/Utils/Iterables.php', + 'Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\ReflectionMethod' => __DIR__ . '/..' . '/nette/utils/src/Utils/ReflectionMethod.php', + 'Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\Type' => __DIR__ . '/..' . '/nette/utils/src/Utils/Type.php', + 'Nette\\Utils\\UnknownImageFileException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => __DIR__ . '/..' . '/nette/utils/src/Utils/Validators.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/tools/.phpstan/vendor/composer/installed.json b/tools/.phpstan/vendor/composer/installed.json new file mode 100644 index 0000000..e7a7c7b --- /dev/null +++ b/tools/.phpstan/vendor/composer/installed.json @@ -0,0 +1,400 @@ +{ + "packages": [ + { + "name": "ergebnis/phpstan-rules", + "version": "2.8.0", + "version_normalized": "2.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/phpstan-rules.git", + "reference": "30e790621fbad05573ef9cd355279fff5122e080" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/30e790621fbad05573ef9cd355279fff5122e080", + "reference": "30e790621fbad05573ef9cd355279fff5122e080", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "phpstan/phpstan": "^2.0.0" + }, + "require-dev": { + "doctrine/orm": "^2.20.0 || ^3.3.0", + "ergebnis/composer-normalize": "^2.45.0", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.43.0", + "ergebnis/phpunit-slow-test-detector": "^2.18.0", + "nette/di": "^3.1.10", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "phpunit/phpunit": "^9.6.21", + "psr/container": "^2.0.2", + "symfony/finder": "^5.4.45", + "symfony/process": "^5.4.47" + }, + "time": "2025-02-18T11:20:05+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides rules for phpstan/phpstan.", + "homepage": "https://github.com/ergebnis/phpstan-rules", + "keywords": [ + "PHPStan", + "phpstan-rules" + ], + "support": { + "issues": "https://github.com/ergebnis/phpstan-rules/issues", + "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/phpstan-rules" + }, + "install-path": "../ergebnis/phpstan-rules" + }, + { + "name": "nette/utils", + "version": "v4.0.5", + "version_normalized": "4.0.5.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "shasum": "" + }, + "require": { + "php": "8.0 - 8.4" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.5", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "time": "2024-08-07T15:39:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.5" + }, + "install-path": "../nette/utils" + }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "version_normalized": "1.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "time": "2024-09-04T20:21:43+00:00", + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "install-path": "../phpstan/extension-installer" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.8", + "version_normalized": "2.1.8.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f9adff3b87c03b12cc7e46a30a524648e497758f", + "reference": "f9adff3b87c03b12cc7e46a30a524648e497758f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "time": "2025-03-09T09:30:48+00:00", + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "install-path": "../phpstan/phpstan" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.3", + "version_normalized": "2.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba", + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "time": "2025-01-21T10:52:14+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3" + }, + "install-path": "../phpstan/phpstan-strict-rules" + }, + { + "name": "tomasvotruba/type-coverage", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/type-coverage.git", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/d033429580f2c18bda538fa44f2939236a990e0c", + "reference": "d033429580f2c18bda538fa44f2939236a990e0c", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2 || ^4.0", + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "time": "2025-01-07T00:10:26+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Measure type coverage of your project", + "keywords": [ + "phpstan-extension", + "static analysis" + ], + "support": { + "issues": "https://github.com/TomasVotruba/type-coverage/issues", + "source": "https://github.com/TomasVotruba/type-coverage/tree/2.0.2" + }, + "funding": [ + { + "url": "https://www.paypal.me/rectorphp", + "type": "custom" + }, + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "install-path": "../tomasvotruba/type-coverage" + } + ], + "dev": true, + "dev-package-names": [ + "ergebnis/phpstan-rules", + "nette/utils", + "phpstan/extension-installer", + "phpstan/phpstan", + "phpstan/phpstan-strict-rules", + "tomasvotruba/type-coverage" + ] +} diff --git a/tools/.phpstan/vendor/composer/installed.php b/tools/.phpstan/vendor/composer/installed.php new file mode 100644 index 0000000..37d6777 --- /dev/null +++ b/tools/.phpstan/vendor/composer/installed.php @@ -0,0 +1,77 @@ + array( + 'name' => '__root__', + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => 'ff662814b51b466b32c93fe10e75bcb7145ef305', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => 'ff662814b51b466b32c93fe10e75bcb7145ef305', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'ergebnis/phpstan-rules' => array( + 'pretty_version' => '2.8.0', + 'version' => '2.8.0.0', + 'reference' => '30e790621fbad05573ef9cd355279fff5122e080', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../ergebnis/phpstan-rules', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'nette/utils' => array( + 'pretty_version' => 'v4.0.5', + 'version' => '4.0.5.0', + 'reference' => '736c567e257dbe0fcf6ce81b4d6dbe05c6899f96', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nette/utils', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/extension-installer' => array( + 'pretty_version' => '1.4.3', + 'version' => '1.4.3.0', + 'reference' => '85e90b3942d06b2326fba0403ec24fe912372936', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../phpstan/extension-installer', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/phpstan' => array( + 'pretty_version' => '2.1.8', + 'version' => '2.1.8.0', + 'reference' => 'f9adff3b87c03b12cc7e46a30a524648e497758f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpstan/phpstan', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/phpstan-strict-rules' => array( + 'pretty_version' => '2.0.3', + 'version' => '2.0.3.0', + 'reference' => '8b88b5f818bfa301e0c99154ab622dace071c3ba', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../phpstan/phpstan-strict-rules', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'tomasvotruba/type-coverage' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'd033429580f2c18bda538fa44f2939236a990e0c', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../tomasvotruba/type-coverage', + 'aliases' => array(), + 'dev_requirement' => true, + ), + ), +); diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md new file mode 100644 index 0000000..8b8d5ce --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/CHANGELOG.md @@ -0,0 +1,637 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +For a full diff see [`2.8.0...main`][2.8.0...main]. + +## [`2.8.0`][2.8.0] + +For a full diff see [`2.7.0...2.8.0`][2.7.0...2.8.0]. + +### Added + +- Added `allRules` parameter to allow disabling and enabling all rules ([#913]), by [@localheinz] +- Added `Expressions\NoAssignByReferenceRule`, which reports an error when a variable is assigned by reference ([#914]), by [@localheinz] + +## [`2.7.0`][2.7.0] + +For a full diff see [`2.6.1...2.7.0`][2.6.1...2.7.0]. + +### Added + +- Added `Closures\NoParameterPassedByReferenceRule`, `Functions\NoParameterPassedByReferenceRule`, `Methods\NoParameterPassedByReferenceRule`, which report an error when a closure, a function, or a method has a parameter that is passed by reference ([#911]), by [@localheinz] +- Added `Functions\NoReturnByReferenceRule` and `Methods\NoReturnByReferenceRule`, which report an error when a function or a method returns by reference ([#912]), by [@localheinz] + +## [`2.6.1`][2.6.1] + +For a full diff see [`2.6.0...2.6.1`][2.6.0...2.6.1]. + +### Fixed + +- Adjusted `Methods\NoParameterWithNullableTypeDeclarationRule` to use the appropriate error identifier ([#902]), by [@manuelkiessling] + +## [`2.6.0`][2.6.0] + +For a full diff see [`2.5.2...2.6.0`][2.5.2...2.6.0]. + +### Added + +- Added support for `phpstan/phpstan:^2.0.0` ([#873]), by [@localheinz] + +## [`2.5.2`][2.5.2] + +For a full diff see [`2.5.1...2.5.2`][2.5.1...2.5.2]. + +### Fixed + +- Adjusted `Closures\NoNullableReturnTypeDeclarationRule`, `Closures\NoParameterWithNullableTypeDeclarationRule`, `Functions\NoNullableReturnTypeDeclarationRule`, `Functions\NoParameterWithNullableTypeDeclarationRule`, `Methods\NoNullableReturnTypeDeclarationRule`, `Methods\NoParameterWithNullableTypeDeclarationRule` to detect cases where `null` is referenced with incorrect case or relative to the root namespace ([#897]), by [@localheinz] + +## [`2.5.1`][2.5.1] + +For a full diff see [`2.5.0...2.5.1`][2.5.0...2.5.1]. + +### Fixed + +- Returned rule with error identifier ([#882]), by [@localheinz] +- Adjusted `Methods\FinalInAbstractClassRule` to ignore Doctrine embeddables and entities ([#396]), by [@localheinz] +- Adjusted `Expressions\NoCompactRule` to detect usages of `compact()` with incorrect case ([#889]), by [@localheinz] +- Adjusted `Methods\PrivateInFinalClassRule` to use more appropriate message when detecting a `protected` method in an anonymous class ([#890]), by [@localheinz] +- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods from traits ([#891]), by [@localheinz] +- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` attributes requiring methods to be `protected` ([#863]), by [@cosmastech] +- Adjusted `Methods\PrivateInFinalClassRule` to ignore `protected` methods with `phpunit/phpunit` annotations requiring methods to be `protected` ([#895]), by [@cosmastech] + +## [`2.5.0`][2.5.0] + +For a full diff see [`2.4.0...2.5.0`][2.4.0...2.5.0]. + +### Added + +- Added rule error identifiers ([#875]), by [@localheinz] +- Added support for PHP 8.0 ([#877]), by [@localheinz] +- Added support for PHP 7.4 ([#880]), by [@localheinz] + +### Changed + +- Removed dependency on `nikic/php-parser` ([#878]), by [@localheinz] + +## [`2.4.0`][2.4.0] + +For a full diff see [`2.3.0...2.4.0`][2.3.0...2.4.0]. + +### Added + +- Added support for PHP 8.4 ([#872]), by [@localheinz] + +## [`2.3.0`][2.3.0] + +For a full diff see [`2.2.0...2.3.0`][2.2.0...2.3.0]. + +### Changed + +- Allowed installation on PHP 8.4 ([#862]), by [@localheinz] + +## [`2.2.0`][2.2.0] + +For a full diff see [`2.1.0...2.2.0`][2.1.0...2.2.0]. + +### Changed + +- Allowed installation of `nikic/php-parser:^5.0.0` ([#735]), by [@localheinz] + +## [`2.1.0`][2.1.0] + +For a full diff see [`2.0.0...2.1.0`][2.0.0...2.1.0]. + +### Changed + +- Dropped support for PHP 8.0 ([#567]), by [@localheinz] +- Added support for PHP 8.3 ([#604]), by [@nunomaduro] + +## [`2.0.0`][2.0.0] + +For a full diff see [`1.0.0...2.0.0`][1.0.0...2.0.0]. + +### Added + +- Added `methodsAllowedToUseContainerTypeDeclarations` parameter to allow configuring a list of method names that are allowed to have container parameter type declarations ([#541), by [@localheinz] +- Allowed disabling rules ([#542), by [@localheinz] +- Added support for nullable union types ([#543), by [@localheinz] + +### Changed + +- Dropped support for PHP 7.2 ([#496]), by [@localheinz] +- Dropped support for PHP 7.3 ([#498]), by [@localheinz] +- Dropped support for PHP 7.4 ([#499]), by [@localheinz] +- Added support for PHP 8.2 ([#540]), by [@localheinz] + +### Removed + +- Removed `Expressions\NoEmptyRule` ([#525]), by [@enumag] + +## [`1.0.0`][1.0.0] + +For a full diff see [`0.15.3...1.0.0`][0.15.3...1.0.0]. + +### Changed + +- Added support for `phpstan/phpstan:^1.0.0` and dropped support for non-stable versions of `phpstan/phpstan` ([#381]), by [@rpkamp] + +### Fixed + +- Adjusted `Classes\FinalRule` to not report an error when a non-final class has a `Doctrinbe\ORM\Mapping\Entity` attribute ([#395]), by [@localheinz] + +## [`0.15.3`][0.15.3] + +For a full diff see [`0.15.2...0.15.3`][0.15.2...0.15.3]. + +### Changed + +- Allow installation with PHP 8.0 ([#294]), by [@localheinz] + +## [`0.15.2`][0.15.2] + +For a full diff see [`0.15.1...0.15.2`][0.15.1...0.15.2]. + +### Changed + +- Dropped support for PHP 7.1 ([#259]), by [@localheinz] + +## [`0.15.1`][0.15.1] + +For a full diff see [`0.15.0...0.15.1`][0.15.0...0.15.1]. + +### Changed + +- Adjusted `Methods\FinalInAbstractClass` rule to allow non-`final` `public` constructors in abstract classes ([#248]), by [@Slamdunk] + +## [`0.15.0`][0.15.0] + +For a full diff see [`0.14.4...0.15.0`][0.14.4...0.15.0]. + +### Added + +- Added `Classes\PHPUnit\Framework\TestCaseWithSuffixRule`, which reports an error when a concrete class extending `PHPUnit\Framework\TestCase` does not have a `Test` suffix ([#225]), by [@localheinz] + +## [`0.14.4`][0.14.4] + +For a full diff see [`0.14.3...0.14.4`][0.14.3...0.14.4]. + +### Fixed + +- Ignored classes with `@ORM\Mapping\Entity` annotations in `FinalRule` ([#202]), by [@localheinz] + +## [`0.14.3`][0.14.3] + +For a full diff see [`0.14.2...0.14.3`][0.14.2...0.14.3]. + +### Fixed + +- Ignored first line in `DeclareStrictTypesRule` when it is a shebang ([#186]), by [@Great-Antique] + +## [`0.14.2`][0.14.2] + +For a full diff see [`0.14.1...0.14.2`][0.14.1...0.14.2]. + +### Fixed + +- Brought back support for PHP 7.1 ([#166]), by [@localheinz] + +## [`0.14.1`][0.14.1] + +For a full diff see [`0.14.0...0.14.1`][0.14.0...0.14.1]. + +### Fixed + +- Removed an inappropriate `replace` configuration from `composer.json` ([#161]), by [@localheinz] + +## [`0.14.0`][0.14.0] + +For a full diff see [`0.13.0...0.14.0`][0.13.0...0.14.0]. + +### Changed + +- Allowed installation of `phpstan/phpstan:~0.12.0` ([#147]), by [@localheinz] +- Renamed vendor namespace `Localheinz` to `Ergebnis` after move to [@ergebnis] ([#157]), by [@localheinz] + + Run + + ```sh + composer remove localheinz/phpstan-rules + ``` + + and + + ```sh + composer require ergebnis/phpstan-rules + ``` + + to update. + + Run + + ```sh + find . -type f -exec sed -i '.bak' 's/Localheinz\\PHPStan/Ergebnis\\PHPStan/g' {} \; + ``` + + to replace occurrences of `Localheinz\PHPStan` with `Ergebnis\PHPStan`. + + Run + + ```sh + find -type f -name '*.bak' -delete + ``` + + to delete backup files created in the previous step. + +- Moved parameters into `ergebnis` section to prevent conflicts with global parameters ([#158]), by [@localheinz] + + Where previously `phpstan.neon` looked like the following + + ```neon + parameters: + allowAbstractClasses: true + classesAllowedToBeExtended: [] + classesNotRequiredToBeAbstractOrFinal: [] + interfacesImplementedByContainers: + - Psr\Container\ContainerInterface + ``` + + these parameters now need to be moved into an `ergebnis` section: + + ```diff + parameters: + - allowAbstractClasses: true + - classesAllowedToBeExtended: [] + - classesNotRequiredToBeAbstractOrFinal: [] + - interfacesImplementedByContainers: + - - Psr\Container\ContainerInterface + + ergebnis: + + allowAbstractClasses: true + + classesAllowedToBeExtended: [] + + classesNotRequiredToBeAbstractOrFinal: [] + + interfacesImplementedByContainers: + + - Psr\Container\ContainerInterface + ``` + +### Fixed + +- Dropped support for PHP 7.1 ([#141]), by [@localheinz] + +## [`0.13.0`][0.13.0] + +For a full diff see [`0.12.2...0.13.0`][0.12.2...0.13.0]. + +### Added + +- Added `Methods\PrivateInFinalClassRule` which reports an error when a method in a `final` class is `protected` when it could be `private` ([#126]), by [@localheinz] + +## [`0.12.2`][0.12.2] + +For a full diff see [`0.12.1...0.12.2`][0.12.1...0.12.2]. + +### Fixed + +- Started ignoring interfaces from analysis by `Methods\FinalInAbstractClassRule` to avoid inappropriate errors ([#132]), by [@localheinz] + +## [`0.12.1`][0.12.1] + +For a full diff see [`0.12.0...0.12.1`][0.12.0...0.12.1]. + +### Fixed + +- Started resolving class name in type declaration before attempting to analyze it in the `Methods\NoParameterWithContainerTypeDeclarationRule` to avoid errors where class `self` is not found ([#128]), by [@localheinz] + +## [`0.12.0`][0.12.0] + +For a full diff see [`0.11.0...0.12.0`][0.11.0...0.12.0]. + +### Added + +- Added `Methods\NoParameterWithContainerTypeDeclarationRule`, which reports an error when a method has a type declaration that corresponds to a known dependency injection container or service locator ([#122]), by [@localheinz] +- Added `Methods\FinalInAbstractClassRule`, which reports an error when a concrete `public` or `protected` method in an `abstract` class is not `final` ([#123]), by [@localheinz] + +## [`0.11.0`][0.11.0] + +For a full diff see [`0.10.0...0.11.0`][0.10.0...0.11.0]. + +### Added + +- Added `Files\DeclareStrictTypesRule`, which reports an error when a PHP file does not have a `declare(strict_types=1)` declaration ([#79] +- Added `Expressions\NoEmptyRule`, which reports an error when the language construct `empty()` is used ([#110]), by [@localheinz] +- Added `Expressions\NoEvalRule`, which reports an error when the language construct `eval()` is used ([#112]), by [@localheinz] +- Added `Expressions\NoErrorSuppressionRule`, which reports an error when `@` is used to suppress errors ([#113]), by [@localheinz] +- Added `Expressions\NoCompactRule`, which reports an error when the function `compact()` is used ([#116]), by [@localheinz] +- Added `Statements\NoSwitchRule`, which reports an error when the statement `switch()` is used ([#117]), by [@localheinz] + +### Changed + +- Require at least `nikic/php-parser:^4.2.3` ([#102]), by [@localheinz] +- Require at least `phpstan/phpstan:~0.11.15` ([#103]), by [@localheinz] + +## [`0.10.0`][0.10.0] + +For a full diff see [`0.9.1...0.10.0`][0.9.1...0.10.0]. + +### Changed + +- Require at least `phpstan/phpstan:~0.11.7` ([#91]), by [@localheinz] + +### Fixed + +- Added missing `parametersSchema` configuration to `rules.neon`, which is required for use with `bleedingEdge.neon`, see [`phpstan/phpstan@54a125d`](https://github.com/phpstan/phpstan/commit/54a125df47fa097b792cb9a3e70c49f753f66b85) ([#93]), by [@localheinz] +* +## [`0.9.1`][0.9.1] + +For a full diff see [`0.9.0...0.9.1`][0.9.0...0.9.1]. + +### Changed + +- Allow usage with [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer) ([#89]), by [@localheinz] + +## [`0.9.0`][0.9.0] + +For a full diff see [`0.8.1...0.9.0`][0.8.1...0.9.0]. + +### Changed + +- Adjusted `Classes\FinalRule` to ignore Doctrine entities when they are annotated ([#84]), by [@localheinz] + +## [`0.8.1`][0.8.1] + +For a full diff see [`0.8.0...0.8.1`][0.8.0...0.8.1]. + +### Fixed + +- Actually enable `Expressions\NoIssetRule` ([#83]), by [@localheinz] + +## [`0.8.0`][0.8.0] + +For a full diff see [`0.7.1...0.8.0`][0.7.1...0.8.0]. + +### Added + +- Added `Expressions\NoIssetRule`, which reports an error when the language construct `isset()` is used ([#81]), by [@localheinz] + +## [`0.7.1`][0.7.1] + +For a full diff see [`0.7.0...0.7.1`][0.7.0...0.7.1]. + +### Changed + +- Configured `Classes\NoExtendsRule` to allow a set of default classes to be extended ([#73]), by [@localheinz] + +## [`0.7.0`][0.7.0] + +For a full diff see [`0.6.0...0.7.0`][0.6.0...0.7.0]. + +### Added + +- Added `Classes\NoExtendsRule`, which reports an error when a class extends a class that is not allowed to be extended ([#68]), by [@localheinz] + +## [`0.6.0`][0.6.0] + +For a full diff see [`0.5.0...0.6.0`][0.5.0...0.6.0]. + +### Added + +- Allow installation with `phpstan/phpstan:~0.11.0` ([#65]), by [@localheinz] + +## [`0.5.0`][0.5.0] + +For a full diff see [`0.4.0...0.5.0`][0.4.0...0.5.0]. + +### Added + +- Added `Methods\NoConstructorParameterWithDefaultValueRule`, which reports an error when a constructor of an anonymous class or a class has a parameter with a default value ([#45]), by [@localheinz] +- Added parameters `$allowAbstractClasses` and `$classesNotRequiredToBeAbstractOrFinal` to allow configuration of `Classes`FinalRule` ([#51]), by [@localheinz] + +### Removed + +- Removed `Classes\AbstractOrFinalRule` after merging behaviour into `Classes\FinalRule` ([#51]), by [@localheinz] +- Removed default values from constructor of `Classes\FinalRule` ([#53]), by [@localheinz] + +## [`0.4.0`][0.4.0] + +For a full diff see [`0.3.0...0.4.0`][0.3.0...0.4.0] + +### Changed + +- Removed double-quotes from error messages to be more consistent with error messages generated by `phpstan/phstan` ([#39]), by [@localheinz] +- Modified wording and placement of method, function, and parameter names in error messages to be more consistent with error messages generated by `phpstan/phstan` ([#42]), by [@localheinz] + +## [`0.3.0`][0.3.0] + +For a full diff see [`0.2.0...0.3.0`][0.2.0...0.3.0] + +### Added + +- Added `Functions\NoNullableReturnTypeDeclarationRule`, which reports an error when a function has a nullable return type declaration, and `Methods\NoNullableReturnTypeDeclarationRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a nullable return type declaration ([#16]), by [@localheinz] +- Added `Closures\NoParameterWithNullDefaultValueRule`, which reports an error when a closure has a parameter with `null` as default value ([#26]), by [@localheinz] +- Added `Closures\NoNullableReturnTypeDeclarationRule`, which reports an error when a closure has a nullable return type declaration ([#29]), by [@localheinz] +- Added `Functions\NoParameterWithNullDefaultValueRule`, which reports an error when a function has a parameter with `null` as default value ([#31]), by [@localheinz] +- Added `Methods\NoParameterWithNullDefaultValueRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a parameter with `null` as default value ([#32]), by [@localheinz] +- Added `Closures\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a closure has a parameter with a nullable type declaration ([#33]), by [@localheinz] +- Added `Functions\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a function has a parameter with a nullable type declaration ([#34]), by [@localheinz] +- Added `Methods\NoParameterWithNullableTypeDeclarationRule`, which reports an error when a method declared in an anonymous class, a class, or an interface has a parameter with a nullable type declaration ([#35]), by [@localheinz] +- Extracted `rules.neon`, so you can easily enable all rules by including it in your `phpstan.neon` ([#37]), by [@localheinz] + +## [`0.2.0`][0.2.0] + +For a full diff see [`0.1.0...0.2.0`][0.1.0...0.2.0] + +### Added + +- Added `Classes\FinalRule`, which reports an error when a non-anonymous class is not `final`, ([#4]), by [@localheinz] + +### Changed + +- Added an `$excludeClassNames` argument to the constructors of `Classes\AbstractOrFinalRule` and `Classes\FinalRule` to allow whitelisting of classes, ([#11]), by [@localheinz] + +## [`0.1.0`][0.1.0] + +For a full diff see [`362c7ea...0.1.0`][362c7ea...0.1.0]. + +### Added + +- Added `Classes\AbstractOrFinalRule`, which reports an error when a non-anonymous class is neither `abstract` nor `final`, ([#1]), by [@localheinz] + +[0.1.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.1.0 +[0.2.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.2.0 +[0.3.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.3.0 +[0.4.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.4.0 +[0.5.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.5.0 +[0.6.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.6.0 +[0.7.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.7.0 +[0.7.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.7.1 +[0.8.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.8.0 +[0.8.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.8.1 +[0.9.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.9.0 +[0.9.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.9.1 +[0.10.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.10.0 +[0.11.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.11.0 +[0.12.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.0 +[0.12.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.1 +[0.12.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.12.2 +[0.13.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.13.0 +[0.14.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.0 +[0.14.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.1 +[0.14.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.2 +[0.14.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.3 +[0.14.4]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.14.4 +[0.15.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.0 +[0.15.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.1 +[0.15.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.2 +[0.15.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/0.15.3 +[1.0.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/1.0.0 +[2.0.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.0.0 +[2.1.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.1.0 +[2.2.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.2.0 +[2.3.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.3.0 +[2.4.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.4.0 +[2.5.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.0 +[2.5.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.1 +[2.5.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.5.2 +[2.6.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.6.0 +[2.6.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.6.1 +[2.7.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.7.0 +[2.8.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.8.0 + +[362c7ea...0.1.0]: https://github.com/ergebnis/phpstan-rules/compare/362c7ea...0.1.0 +[0.1.0...0.2.0]: https://github.com/ergebnis/phpstan-rules/compare/0.1.0...0.2.0 +[0.2.0...0.3.0]: https://github.com/ergebnis/phpstan-rules/compare/0.2.0...0.3.0 +[0.3.0...0.4.0]: https://github.com/ergebnis/phpstan-rules/compare/0.3.0...0.4.0 +[0.4.0...0.5.0]: https://github.com/ergebnis/phpstan-rules/compare/0.4.0...0.5.0 +[0.5.0...0.6.0]: https://github.com/ergebnis/phpstan-rules/compare/0.5.0...0.6.0 +[0.6.0...0.7.0]: https://github.com/ergebnis/phpstan-rules/compare/0.6.0...0.7.0 +[0.7.0...0.7.1]: https://github.com/ergebnis/phpstan-rules/compare/0.7.0...0.7.1 +[0.7.1...0.8.0]: https://github.com/ergebnis/phpstan-rules/compare/0.7.1...0.8.0 +[0.8.0...0.8.1]: https://github.com/ergebnis/phpstan-rules/compare/0.8.0...0.8.1 +[0.8.1...0.9.0]: https://github.com/ergebnis/phpstan-rules/compare/0.8.1...0.9.0 +[0.9.0...0.9.1]: https://github.com/ergebnis/phpstan-rules/compare/0.9.0...0.9.1 +[0.9.1...0.10.0]: https://github.com/ergebnis/phpstan-rules/compare/0.9.1...0.10.0 +[0.10.0...0.11.0]: https://github.com/ergebnis/phpstan-rules/compare/0.10.0...0.11.0 +[0.11.0...0.12.0]: https://github.com/ergebnis/phpstan-rules/compare/0.11.0...0.12.0 +[0.12.0...0.12.1]: https://github.com/ergebnis/phpstan-rules/compare/0.12.0...0.12.1 +[0.12.1...0.12.2]: https://github.com/ergebnis/phpstan-rules/compare/0.12.1...0.12.2 +[0.12.2...0.13.0]: https://github.com/ergebnis/phpstan-rules/compare/0.12.2...0.13.0 +[0.13.0...0.14.0]: https://github.com/ergebnis/phpstan-rules/compare/0.13.0...0.14.0 +[0.14.0...0.14.1]: https://github.com/ergebnis/phpstan-rules/compare/0.14.0...0.14.1 +[0.14.1...0.14.2]: https://github.com/ergebnis/phpstan-rules/compare/0.14.1...0.14.2 +[0.14.2...0.14.3]: https://github.com/ergebnis/phpstan-rules/compare/0.14.2...0.14.3 +[0.14.3...0.14.4]: https://github.com/ergebnis/phpstan-rules/compare/0.14.3...0.14.4 +[0.14.4...0.15.0]: https://github.com/ergebnis/phpstan-rules/compare/0.14.4...0.15.0 +[0.15.0...0.15.1]: https://github.com/ergebnis/phpstan-rules/compare/0.15.0...0.15.1 +[0.15.1...0.15.2]: https://github.com/ergebnis/phpstan-rules/compare/0.15.1...0.15.2 +[0.15.2...0.15.3]: https://github.com/ergebnis/phpstan-rules/compare/0.15.2...0.15.3 +[0.15.3...1.0.0]: https://github.com/ergebnis/phpstan-rules/compare/0.15.3...1.0.0 +[1.0.0...2.0.0]: https://github.com/ergebnis/phpstan-rules/compare/1.0.0...2.0.0 +[2.0.0...2.1.0]: https://github.com/ergebnis/phpstan-rules/compare/2.0.0...2.1.0 +[2.1.0...2.2.0]: https://github.com/ergebnis/phpstan-rules/compare/2.1.0...2.2.0 +[2.2.0...2.3.0]: https://github.com/ergebnis/phpstan-rules/compare/2.2.0...2.3.0 +[2.3.0...2.4.0]: https://github.com/ergebnis/phpstan-rules/compare/2.3.0...2.4.0 +[2.4.0...2.5.0]: https://github.com/ergebnis/phpstan-rules/compare/2.4.0...2.5.0 +[2.5.0...2.5.1]: https://github.com/ergebnis/phpstan-rules/compare/2.5.0...2.5.1 +[2.5.1...2.5.2]: https://github.com/ergebnis/phpstan-rules/compare/2.5.1...2.5.2 +[2.5.2...2.6.0]: https://github.com/ergebnis/phpstan-rules/compare/2.5.2...2.6.0 +[2.6.0...2.6.1]: https://github.com/ergebnis/phpstan-rules/compare/2.6.0...2.6.1 +[2.6.1...2.7.0]: https://github.com/ergebnis/phpstan-rules/compare/2.6.1...2.7.0 +[2.7.0...2.8.0]: https://github.com/ergebnis/phpstan-rules/compare/2.7.0...2.8.0 +[2.8.0...main]: https://github.com/ergebnis/phpstan-rules/compare/2.8.0...main + +[#1]: https://github.com/ergebnis/phpstan-rules/pull/1 +[#4]: https://github.com/ergebnis/phpstan-rules/pull/4 +[#11]: https://github.com/ergebnis/phpstan-rules/pull/11 +[#16]: https://github.com/ergebnis/phpstan-rules/pull/16 +[#26]: https://github.com/ergebnis/phpstan-rules/pull/26 +[#29]: https://github.com/ergebnis/phpstan-rules/pull/29 +[#31]: https://github.com/ergebnis/phpstan-rules/pull/31 +[#32]: https://github.com/ergebnis/phpstan-rules/pull/32 +[#33]: https://github.com/ergebnis/phpstan-rules/pull/33 +[#34]: https://github.com/ergebnis/phpstan-rules/pull/34 +[#35]: https://github.com/ergebnis/phpstan-rules/pull/35 +[#37]: https://github.com/ergebnis/phpstan-rules/pull/37 +[#39]: https://github.com/ergebnis/phpstan-rules/pull/39 +[#42]: https://github.com/ergebnis/phpstan-rules/pull/42 +[#45]: https://github.com/ergebnis/phpstan-rules/pull/45 +[#51]: https://github.com/ergebnis/phpstan-rules/pull/51 +[#53]: https://github.com/ergebnis/phpstan-rules/pull/53 +[#65]: https://github.com/ergebnis/phpstan-rules/pull/65 +[#68]: https://github.com/ergebnis/phpstan-rules/pull/68 +[#73]: https://github.com/ergebnis/phpstan-rules/pull/73 +[#79]: https://github.com/ergebnis/phpstan-rules/pull/79 +[#81]: https://github.com/ergebnis/phpstan-rules/pull/81 +[#83]: https://github.com/ergebnis/phpstan-rules/pull/83 +[#84]: https://github.com/ergebnis/phpstan-rules/pull/84 +[#89]: https://github.com/ergebnis/phpstan-rules/pull/89 +[#91]: https://github.com/ergebnis/phpstan-rules/pull/91 +[#93]: https://github.com/ergebnis/phpstan-rules/pull/93 +[#102]: https://github.com/ergebnis/phpstan-rules/pull/102 +[#103]: https://github.com/ergebnis/phpstan-rules/pull/103 +[#110]: https://github.com/ergebnis/phpstan-rules/pull/110 +[#112]: https://github.com/ergebnis/phpstan-rules/pull/112 +[#113]: https://github.com/ergebnis/phpstan-rules/pull/113 +[#116]: https://github.com/ergebnis/phpstan-rules/pull/116 +[#117]: https://github.com/ergebnis/phpstan-rules/pull/117 +[#122]: https://github.com/ergebnis/phpstan-rules/pull/122 +[#123]: https://github.com/ergebnis/phpstan-rules/pull/123 +[#126]: https://github.com/ergebnis/phpstan-rules/pull/126 +[#128]: https://github.com/ergebnis/phpstan-rules/pull/128 +[#132]: https://github.com/ergebnis/phpstan-rules/pull/132 +[#141]: https://github.com/ergebnis/phpstan-rules/pull/141 +[#147]: https://github.com/ergebnis/phpstan-rules/pull/147 +[#157]: https://github.com/ergebnis/phpstan-rules/pull/157 +[#158]: https://github.com/ergebnis/phpstan-rules/pull/158 +[#161]: https://github.com/ergebnis/phpstan-rules/pull/161 +[#166]: https://github.com/ergebnis/phpstan-rules/pull/166 +[#186]: https://github.com/ergebnis/phpstan-rules/pull/186 +[#202]: https://github.com/ergebnis/phpstan-rules/pull/202 +[#225]: https://github.com/ergebnis/phpstan-rules/pull/225 +[#248]: https://github.com/ergebnis/phpstan-rules/pull/248 +[#259]: https://github.com/ergebnis/phpstan-rules/pull/259 +[#294]: https://github.com/ergebnis/phpstan-rules/pull/294 +[#381]: https://github.com/ergebnis/phpstan-rules/pull/381 +[#395]: https://github.com/ergebnis/phpstan-rules/pull/395 +[#396]: https://github.com/ergebnis/phpstan-rules/pull/396 +[#496]: https://github.com/ergebnis/phpstan-rules/pull/496 +[#498]: https://github.com/ergebnis/phpstan-rules/pull/498 +[#499]: https://github.com/ergebnis/phpstan-rules/pull/498 +[#525]: https://github.com/ergebnis/phpstan-rules/pull/525 +[#540]: https://github.com/ergebnis/phpstan-rules/pull/540 +[#541]: https://github.com/ergebnis/phpstan-rules/pull/541 +[#542]: https://github.com/ergebnis/phpstan-rules/pull/542 +[#543]: https://github.com/ergebnis/phpstan-rules/pull/543 +[#567]: https://github.com/ergebnis/phpstan-rules/pull/567 +[#735]: https://github.com/ergebnis/phpstan-rules/pull/735 +[#862]: https://github.com/ergebnis/phpstan-rules/pull/862 +[#863]: https://github.com/ergebnis/phpstan-rules/pull/863 +[#872]: https://github.com/ergebnis/phpstan-rules/pull/872 +[#873]: https://github.com/ergebnis/phpstan-rules/pull/873 +[#875]: https://github.com/ergebnis/phpstan-rules/pull/875 +[#877]: https://github.com/ergebnis/phpstan-rules/pull/877 +[#878]: https://github.com/ergebnis/phpstan-rules/pull/878 +[#880]: https://github.com/ergebnis/phpstan-rules/pull/880 +[#882]: https://github.com/ergebnis/phpstan-rules/pull/882 +[#889]: https://github.com/ergebnis/phpstan-rules/pull/889 +[#890]: https://github.com/ergebnis/phpstan-rules/pull/890 +[#891]: https://github.com/ergebnis/phpstan-rules/pull/891 +[#895]: https://github.com/ergebnis/phpstan-rules/pull/895 +[#897]: https://github.com/ergebnis/phpstan-rules/pull/897 +[#902]: https://github.com/ergebnis/phpstan-rules/pull/902 +[#911]: https://github.com/ergebnis/phpstan-rules/pull/911 +[#912]: https://github.com/ergebnis/phpstan-rules/pull/912 +[#913]: https://github.com/ergebnis/phpstan-rules/pull/913 +[#914]: https://github.com/ergebnis/phpstan-rules/pull/914 + +[@cosmastech]: https://github.com/cosmastech +[@enumag]: https://github.com/enumag +[@ergebnis]: https://github.com/ergebnis +[@Great-Antique]: https://github.com/Great-Antique +[@localheinz]: https://github.com/localheinz +[@manuelkiessling]: https://github.com/manuelkiessling +[@nunomaduro]: https://github.com/nunomaduro +[@rpkamp]: https://github.com/rpkamp +[@Slamdunk]: https://github.com/Slamdunk diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md new file mode 100644 index 0000000..130e719 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/LICENSE.md @@ -0,0 +1,16 @@ +# The MIT License (MIT) + +Copyright (c) 2018-2025 Andreas Möller + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the _Software_), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED **AS IS**, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md new file mode 100644 index 0000000..8296743 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/README.md @@ -0,0 +1,683 @@ +# phpstan-rules + +[![Integrate](https://github.com/ergebnis/phpstan-rules/workflows/Integrate/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) +[![Merge](https://github.com/ergebnis/phpstan-rules/workflows/Merge/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) +[![Release](https://github.com/ergebnis/phpstan-rules/workflows/Release/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) +[![Renew](https://github.com/ergebnis/phpstan-rules/workflows/Renew/badge.svg)](https://github.com/ergebnis/phpstan-rules/actions) + +[![Code Coverage](https://codecov.io/gh/ergebnis/phpstan-rules/branch/main/graph/badge.svg)](https://codecov.io/gh/ergebnis/phpstan-rules) + +[![Latest Stable Version](https://poser.pugx.org/ergebnis/phpstan-rules/v/stable)](https://packagist.org/packages/ergebnis/phpstan-rules) +[![Total Downloads](https://poser.pugx.org/ergebnis/phpstan-rules/downloads)](https://packagist.org/packages/ergebnis/phpstan-rules) +[![Monthly Downloads](http://poser.pugx.org/ergebnis/phpstan-rules/d/monthly)](https://packagist.org/packages/ergebnis/phpstan-rules) + +This project provides a [`composer`](https://getcomposer.org) package with rules for [`phpstan/phpstan`](https://github.com/phpstan/phpstan). + +## Installation + +Run + +```sh +composer require --dev ergebnis/phpstan-rules +``` + +## Usage + +All of the [rules](https://github.com/ergebnis/phpstan-rules#rules) provided (and used) by this library are included in [`rules.neon`](rules.neon). + +When you are using [`phpstan/extension-installer`](https://github.com/phpstan/extension-installer), `rules.neon` will be automatically included. + +Otherwise you need to include `rules.neon` in your `phpstan.neon`: + +```neon +includes: + - vendor/ergebnis/phpstan-rules/rules.neon +``` + +:bulb: You probably want to use these rules on top of the rules provided by: + +- [`phpstan/phpstan`](https://github.com/phpstan/phpstan) +- [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules) +- [`phpstan/phpstan-strict-rules`](https://github.com/phpstan/phpstan-strict-rules) + +## Rules + +This package provides the following rules for use with [`phpstan/phpstan`](https://github.com/phpstan/phpstan): + +- [`Ergebnis\PHPStan\Rules\Classes\FinalRule`](https://github.com/ergebnis/phpstan-rules#classesfinalrule) +- [`Ergebnis\PHPStan\Rules\Classes\NoExtendsRule`](https://github.com/ergebnis/phpstan-rules#classesnoextendsrule) +- [`Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule`](https://github.com/ergebnis/phpstan-rules#classesphpunitframeworktestcasewithsuffixrule) +- [`Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#closuresnonullablereturntypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterpassedbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterwithnullabletypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#closuresnoparameterwithnulldefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoassignbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoCompactRule`](https://github.com/ergebnis/phpstan-rules#expressionsnocompactrule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoerrorsuppressionrule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoEvalRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoevalrule) +- [`Ergebnis\PHPStan\Rules\Expressions\NoIssetRule`](https://github.com/ergebnis/phpstan-rules#expressionsnoissetrule) +- [`Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule`](https://github.com/ergebnis/phpstan-rules#filesdeclarestricttypesrule) +- [`Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnonullablereturntypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterpassedbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnullabletypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#functionsnoparameterwithnulldefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#functionsnoreturnbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule`](https://github.com/ergebnis/phpstan-rules#methodsfinalinabstractclassrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoconstructorparameterwithdefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnonullablereturntypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterpassedbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithcontainertypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnullabletypedeclarationrule) +- [`Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule`](https://github.com/ergebnis/phpstan-rules#methodsnoparameterwithnulldefaultvaluerule) +- [`Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule`](https://github.com/ergebnis/phpstan-rules#methodsnoreturnbyreferencerule) +- [`Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule`](https://github.com/ergebnis/phpstan-rules#methodsprivateinfinalclassrule) +- [`Ergebnis\PHPStan\Rules\Statements\NoSwitchRule`](https://github.com/ergebnis/phpstan-rules#statementsnoswitchrule) + +### Classes + +#### `Classes\FinalRule` + +This rule reports an error when a non-anonymous class is not `final`. + +:bulb: This rule ignores classes that + +- use `@Entity`, `@ORM\Entity`, or `@ORM\Mapping\Entity` annotations +- use `Doctrine\ORM\Mapping\Entity` attributes + +on the class level. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + final: + enabled: false +``` + +##### Disallowing `abstract` classes + +By default, this rule allows to declare `abstract` classes. + +You can set the `allowAbstractClasses` parameter to `false` to disallow abstract classes. + +```neon +parameters: + ergebnis: + final: + allowAbstractClasses: false +``` + +##### Excluding classes from inspection + +You can set the `classesNotRequiredToBeAbstractOrFinal` parameter to a list of class names that you want to exclude from inspection. + +```neon +parameters: + ergebnis: + final: + classesNotRequiredToBeAbstractOrFinal: + - Foo\Bar\NeitherAbstractNorFinal + - Bar\Baz\NeitherAbstractNorFinal +``` + +#### `Classes\NoExtendsRule` + +This rule reports an error when a class extends another class. + +##### Defaults + +By default, this rule allows the following classes to be extended: + +- [`PHPUnit\Framework\TestCase`](https://github.com/sebastianbergmann/phpunit/blob/7.5.2/src/Framework/TestCase.php) + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noExtends: + enabled: false +``` + +##### Allowing classes to be extended + +You can set the `classesAllowedToBeExtended` parameter to a list of class names that you want to allow to be extended. + +```neon +parameters: + ergebnis: + noExtends: + classesAllowedToBeExtended: + - Ergebnis\PHPStan\Rules\Test\Integration\AbstractTestCase + - Ergebnis\PHPStan\Rules\Test\Integration\AbstractTestCase +``` + +#### `Classes\PHPUnit\Framework\TestCaseWithSuffixRule` + +This rule reports an error when a concrete class is a sub-class of `PHPUnit\Framework\TestCase` but does not have a `Test` suffix. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + testCaseWithSuffix: + enabled: false +``` + +### Closures + +#### `Closures\NoNullableReturnTypeDeclarationRule` + +This rule reports an error when a closure uses a nullable return type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noNullableReturnTypeDeclaration: + enabled: false +``` + +#### `Closures\NoParameterPassedByReferenceRule` + +This rule reports an error when a closure has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterPassedByReference: + enabled: false +``` + +#### `Closures\NoParameterWithNullableTypeDeclarationRule` + +This rule reports an error when a closure has a parameter with a nullable type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullableTypeDeclaration: + enabled: false +``` + +#### `Closures\NoParameterWithNullDefaultValueRule` + +This rule reports an error when a closure has a parameter with `null` as default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullDefaultValue: + enabled: false +``` + +### Expressions + +#### `Expressions\NoAssignByReferenceRule` + +This rule reports an error when [a variable is assigned by reference](https://www.php.net/manual/en/language.references.whatdo.php#language.references.whatdo.assign). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noAssignByReference: + enabled: false +``` + +#### `Expressions\NoCompactRule` + +This rule reports an error when the function [`compact()`](https://www.php.net/compact) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noCompact: + enabled: false +``` + +#### `Expressions\NoEvalRule` + +This rule reports an error when the language construct [`eval()`](https://www.php.net/eval) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noEval: + enabled: false +``` + +#### `Expressions\NoErrorSuppressionRule` + +This rule reports an error when [`@`](https://www.php.net/manual/en/language.operators.errorcontrol.php) is used to suppress errors. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noErrorSuppression: + enabled: false +``` + +#### `Expressions\NoIssetRule` + +This rule reports an error when the language construct [`isset()`](https://www.php.net/isset) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noIsset: + enabled: false +``` + +### Files + +#### `Files\DeclareStrictTypesRule` + +This rule reports an error when a non-empty file does not contain a `declare(strict_types=1)` declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + declareStrictTypes: + enabled: false +``` + +### Functions + +#### `Functions\NoNullableReturnTypeDeclarationRule` + +This rule reports an error when a function uses a nullable return type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noNullableReturnTypeDeclaration: + enabled: false +``` + +#### `Functions\NoParameterPassedByReferenceRule` + +This rule reports an error when a function has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterPassedByReference: + enabled: false +``` + +#### `Functions\NoParameterWithNullableTypeDeclarationRule` + +This rule reports an error when a function has a parameter with a nullable type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullableTypeDeclaration: + enabled: false +``` + +#### `Functions\NoParameterWithNullDefaultValueRule` + +This rule reports an error when a function has a parameter with `null` as default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullDefaultValue: + enabled: false +``` + +#### `Functions\NoReturnByReferenceRule` + +This rule reports an error when a function [returns by reference](https://www.php.net/manual/en/language.references.return.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noReturnByReference: + enabled: false +``` + +### Methods + +#### `Methods\FinalInAbstractClassRule` + +This rule reports an error when a concrete `public` or `protected `method in an `abstract` class is not `final`. + +:bulb: This rule ignores + +- Doctrine embeddables +- Doctrine entities + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + finalInAbstractClass: + enabled: false +``` + +#### `Methods\NoConstructorParameterWithDefaultValueRule` + +This rule reports an error when a constructor declared in + +- an anonymous class +- a class + +has a default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noConstructorParameterWithDefaultValue: + enabled: false +``` + +#### `Methods\NoParameterPassedByReferenceRule` + +This rule reports an error when a method has a parameter that is [passed by reference](https://www.php.net/manual/en/language.references.pass.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterPassedByReference: + enabled: false +``` + +#### `Methods\NoNullableReturnTypeDeclarationRule` + +This rule reports an error when a method declared in + +- an anonymous class +- a class +- an interface + +uses a nullable return type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noNullableReturnTypeDeclaration: + enabled: false +``` + +#### `Methods\NoParameterWithContainerTypeDeclarationRule` + +This rule reports an error when a method has a type declaration for a known dependency injection container or service locator. + +##### Defaults + +By default, this rule disallows the use of type declarations indicating an implementation of + +- [`Psr\Container\ContainerInterface`](https://github.com/php-fig/container/blob/1.0.0/src/ContainerInterface.php) + +is expected to be injected into a method. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithContainerTypeDeclaration: + enabled: false +``` + +##### Configuring container interfaces + +You can set the `interfacesImplementedByContainers` parameter to a list of interface names of additional containers and service locators. + +```neon +parameters: + ergebnis: + noParameterWithContainerTypeDeclaration: + interfacesImplementedByContainers: + - Fancy\DependencyInjection\ContainerInterface + - Other\ServiceLocatorInterface +``` + +##### Configuring methods allowed to use parameters with container type declarations + +You can set the `methodsAllowedToUseContainerTypeDeclarations` parameter to a list of method names that are allowed to use parameters with container type declarations. + +```neon +parameters: + ergebnis: + noParameterWithContainerTypeDeclaration: + methodsAllowedToUseContainerTypeDeclarations: + - loadExtension +``` + +#### `Methods\NoParameterWithNullableTypeDeclarationRule` + +This rule reports an error when a method declared in + +- an anonymous class +- a class +- an interface + +has a parameter with a nullable type declaration. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullableTypeDeclaration: + enabled: false +``` + +#### `Methods\NoParameterWithNullDefaultValueRule` + +This rule reports an error when a method declared in + +- an anonymous class +- a class +- an interface + +has a parameter with `null` as default value. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noParameterWithNullDefaultValue: + enabled: false +``` + +#### `Functions\NoReturnByReferenceRule` + +This rule reports an error when a method [returns by reference](https://www.php.net/manual/en/language.references.return.php). + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noReturnByReference: + enabled: false +``` + +#### `Methods\PrivateInFinalClassRule` + +This rule reports an error when a method in a `final` class is `protected` but could be `private`. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + privateInFinalClass: + enabled: false +``` + +### Statements + +#### `Statements\NoSwitchRule` + +This rule reports an error when the statement [`switch()`](https://www.php.net/manual/control-structures.switch.php) is used. + +##### Disabling the rule + +You can set the `enabled` parameter to `false` to disable this rule. + +```neon +parameters: + ergebnis: + noSwitch: + enabled: false +``` + +## Disabling all rules + +You can disable all rules using the `allRules` configuration parameter: + +```neon +parameters: + ergebnis: + allRules: false +``` + +## Enabling rules one-by-one + +If you have disabled all rules using the `allRules` configuration parameter, you can re-enable individual rules with their corresponding configuration parameters: + +```neon +parameters: + ergebnis: + allRules: false + privateInFinalClass: + enabled: true +``` + +## Changelog + +The maintainers of this project record notable changes to this project in a [changelog](CHANGELOG.md). + +## Contributing + +The maintainers of this project suggest following the [contribution guide](.github/CONTRIBUTING.md). + +## Code of Conduct + +The maintainers of this project ask contributors to follow the [code of conduct](https://github.com/ergebnis/.github/blob/main/CODE_OF_CONDUCT.md). + +## General Support Policy + +The maintainers of this project provide limited support. + +You can support the maintenance of this project by [sponsoring @localheinz](https://github.com/sponsors/localheinz) or [requesting an invoice for services related to this project](mailto:am@localheinz.com?subject=ergebnis/phpstan-rules:%20Requesting%20invoice%20for%20services). + +## PHP Version Support Policy + +This project supports PHP versions with [active and security support](https://www.php.net/supported-versions.php). + +The maintainers of this project add support for a PHP version following its initial release and drop support for a PHP version when it has reached the end of security support. + +## Security Policy + +This project has a [security policy](.github/SECURITY.md). + +## License + +This project uses the [MIT license](LICENSE.md). + +## Credits + +The method [`FinalRule::isWhitelistedClass()`](src/Classes/FinalRule.php) is inspired by the work on [`FinalClassFixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalClassFixer.php) and [`FinalInternalClassFixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalInternalClassFixer.php), contributed by [Dariusz Rumiński](https://github.com/keradus), [Filippo Tessarotto](https://github.com/Slamdunk), and [Spacepossum](https://github.com/SpacePossum) for [`friendsofphp/php-cs-fixer`](https://github.com/FriendsOfPHP/PHP-CS-Fixer) (originally licensed under MIT). + +## Social + +Follow [@localheinz](https://twitter.com/intent/follow?screen_name=localheinz) and [@ergebnis](https://twitter.com/intent/follow?screen_name=ergebnis) on Twitter. diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json new file mode 100644 index 0000000..89d6871 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/composer.json @@ -0,0 +1,76 @@ +{ + "name": "ergebnis/phpstan-rules", + "description": "Provides rules for phpstan/phpstan.", + "license": "MIT", + "type": "phpstan-extension", + "keywords": [ + "phpstan", + "phpstan-rules" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "homepage": "https://github.com/ergebnis/phpstan-rules", + "support": { + "issues": "https://github.com/ergebnis/phpstan-rules/issues", + "source": "https://github.com/ergebnis/phpstan-rules", + "security": "https://github.com/ergebnis/phpstan-rules/blob/main/.github/SECURITY.md" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "ext-mbstring": "*", + "phpstan/phpstan": "^2.0.0" + }, + "require-dev": { + "doctrine/orm": "^2.20.0 || ^3.3.0", + "ergebnis/composer-normalize": "^2.45.0", + "ergebnis/license": "^2.6.0", + "ergebnis/php-cs-fixer-config": "^6.43.0", + "ergebnis/phpunit-slow-test-detector": "^2.18.0", + "nette/di": "^3.1.10", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "phpunit/phpunit": "^9.6.21", + "psr/container": "^2.0.2", + "symfony/finder": "^5.4.45", + "symfony/process": "^5.4.47" + }, + "autoload": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Ergebnis\\PHPStan\\Rules\\Test\\": "test/" + } + }, + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true, + "infection/extension-installer": true, + "phpstan/extension-installer": true + }, + "audit": { + "abandoned": "report" + }, + "platform": { + "php": "7.4.33" + }, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon b/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon new file mode 100644 index 0000000..a81a7e8 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/rules.neon @@ -0,0 +1,263 @@ +conditionalTags: + Ergebnis\PHPStan\Rules\Classes\FinalRule: + phpstan.rules.rule: %ergebnis.final.enabled% + Ergebnis\PHPStan\Rules\Classes\NoExtendsRule: + phpstan.rules.rule: %ergebnis.noExtends.enabled% + Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule: + phpstan.rules.rule: %ergebnis.testCaseWithSuffix.enabled% + Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule: + phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled% + Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule: + phpstan.rules.rule: %ergebnis.noAssignByReference.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoCompactRule: + phpstan.rules.rule: %ergebnis.noCompact.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule: + phpstan.rules.rule: %ergebnis.noErrorSuppression.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoEvalRule: + phpstan.rules.rule: %ergebnis.noEval.enabled% + Ergebnis\PHPStan\Rules\Expressions\NoIssetRule: + phpstan.rules.rule: %ergebnis.noIsset.enabled% + Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule: + phpstan.rules.rule: %ergebnis.declareStrictTypes.enabled% + Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule: + phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled% + Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled% + Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule: + phpstan.rules.rule: %ergebnis.noReturnByReference.enabled% + Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule: + phpstan.rules.rule: %ergebnis.finalInAbstractClass.enabled% + Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule: + phpstan.rules.rule: %ergebnis.noConstructorParameterWithDefaultValue.enabled% + Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noNullableReturnTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule: + phpstan.rules.rule: %ergebnis.noParameterPassedByReference.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithContainerTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullableTypeDeclaration.enabled% + Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule: + phpstan.rules.rule: %ergebnis.noParameterWithNullDefaultValue.enabled% + Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule: + phpstan.rules.rule: %ergebnis.noReturnByReference.enabled% + Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule: + phpstan.rules.rule: %ergebnis.privateInFinalClass.enabled% + Ergebnis\PHPStan\Rules\Statements\NoSwitchRule: + phpstan.rules.rule: %ergebnis.noSwitch.enabled% + +parameters: + ergebnis: + allRules: true + declareStrictTypes: + enabled: %ergebnis.allRules% + final: + allowAbstractClasses: true + classesNotRequiredToBeAbstractOrFinal: [] + enabled: %ergebnis.allRules% + finalInAbstractClass: + enabled: %ergebnis.allRules% + noAssignByReference: + enabled: %ergebnis.allRules% + noCompact: + enabled: %ergebnis.allRules% + noConstructorParameterWithDefaultValue: + enabled: %ergebnis.allRules% + noErrorSuppression: + enabled: %ergebnis.allRules% + noEval: + enabled: %ergebnis.allRules% + noExtends: + classesAllowedToBeExtended: [] + enabled: %ergebnis.allRules% + noIsset: + enabled: %ergebnis.allRules% + noNullableReturnTypeDeclaration: + enabled: %ergebnis.allRules% + noParameterPassedByReference: + enabled: %ergebnis.allRules% + noParameterWithContainerTypeDeclaration: + enabled: %ergebnis.allRules% + interfacesImplementedByContainers: + - Psr\Container\ContainerInterface + methodsAllowedToUseContainerTypeDeclarations: [] + noParameterWithNullableTypeDeclaration: + enabled: %ergebnis.allRules% + noParameterWithNullDefaultValue: + enabled: %ergebnis.allRules% + noReturnByReference: + enabled: %ergebnis.allRules% + noSwitch: + enabled: %ergebnis.allRules% + privateInFinalClass: + enabled: %ergebnis.allRules% + testCaseWithSuffix: + enabled: %ergebnis.allRules% + +parametersSchema: + ergebnis: structure([ + allRules: bool() + declareStrictTypes: structure([ + enabled: bool(), + ]) + final: structure([ + allowAbstractClasses: bool() + classesNotRequiredToBeAbstractOrFinal: listOf(string()) + enabled: bool(), + ]) + finalInAbstractClass: structure([ + enabled: bool(), + ]) + noAssignByReference: structure([ + enabled: bool(), + ]) + noCompact: structure([ + enabled: bool(), + ]) + noConstructorParameterWithDefaultValue: structure([ + enabled: bool(), + ]) + noErrorSuppression: structure([ + enabled: bool(), + ]) + noExtends: structure([ + classesAllowedToBeExtended: listOf(string()) + enabled: bool(), + ]) + noEval: structure([ + enabled: bool(), + ]) + noIsset: structure([ + enabled: bool(), + ]) + noNullableReturnTypeDeclaration: structure([ + enabled: bool(), + ]) + noParameterPassedByReference: structure([ + enabled: bool(), + ]) + noParameterWithContainerTypeDeclaration: structure([ + enabled: bool(), + interfacesImplementedByContainers: listOf(string()) + methodsAllowedToUseContainerTypeDeclarations: listOf(string()) + ]) + noParameterWithNullableTypeDeclaration: structure([ + enabled: bool(), + ]) + noParameterWithNullDefaultValue: structure([ + enabled: bool(), + ]) + noReturnByReference: structure([ + enabled: bool(), + ]) + noSwitch: structure([ + enabled: bool(), + ]) + privateInFinalClass: structure([ + enabled: bool(), + ]) + testCaseWithSuffix: structure([ + enabled: bool(), + ]) + ]) + +services: + - + class: Ergebnis\PHPStan\Rules\Analyzer + + - + class: Ergebnis\PHPStan\Rules\Classes\FinalRule + arguments: + allowAbstractClasses: %ergebnis.final.allowAbstractClasses% + classesNotRequiredToBeAbstractOrFinal: %ergebnis.final.classesNotRequiredToBeAbstractOrFinal% + + - + class: Ergebnis\PHPStan\Rules\Classes\NoExtendsRule + arguments: + classesAllowedToBeExtended: %ergebnis.noExtends.classesAllowedToBeExtended% + + - + class: Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework\TestCaseWithSuffixRule + + - + class: Ergebnis\PHPStan\Rules\Closures\NoNullableReturnTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Closures\NoParameterPassedByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Closures\NoParameterWithNullableTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoAssignByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoCompactRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoErrorSuppressionRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoEvalRule + + - + class: Ergebnis\PHPStan\Rules\Expressions\NoIssetRule + + - + class: Ergebnis\PHPStan\Rules\Files\DeclareStrictTypesRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoNullableReturnTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoParameterPassedByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullableTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoParameterWithNullDefaultValueRule + + - + class: Ergebnis\PHPStan\Rules\Functions\NoReturnByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Methods\FinalInAbstractClassRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoConstructorParameterWithDefaultValueRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoNullableReturnTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterPassedByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithContainerTypeDeclarationRule + arguments: + interfacesImplementedByContainers: %ergebnis.noParameterWithContainerTypeDeclaration.interfacesImplementedByContainers% + methodsAllowedToUseContainerTypeDeclarations: %ergebnis.noParameterWithContainerTypeDeclaration.methodsAllowedToUseContainerTypeDeclarations% + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullableTypeDeclarationRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoParameterWithNullDefaultValueRule + + - + class: Ergebnis\PHPStan\Rules\Methods\NoReturnByReferenceRule + + - + class: Ergebnis\PHPStan\Rules\Methods\PrivateInFinalClassRule + + - + class: Ergebnis\PHPStan\Rules\Statements\NoSwitchRule diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php new file mode 100644 index 0000000..938cab7 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Analyzer.php @@ -0,0 +1,68 @@ +default instanceof Node\Expr\ConstFetch) { + return false; + } + + return 'null' === $parameter->default->name->toLowerString(); + } + + /** + * @param null|Node\ComplexType|Node\Identifier|Node\Name $typeDeclaration + */ + public function isNullableTypeDeclaration($typeDeclaration): bool + { + if ($typeDeclaration instanceof Node\NullableType) { + return true; + } + + if ($typeDeclaration instanceof Node\UnionType) { + foreach ($typeDeclaration->types as $type) { + if ( + $type instanceof Node\Identifier + && 'null' === $type->toLowerString() + ) { + return true; + } + + if ( + $type instanceof Node\Name\FullyQualified + && $type->hasAttribute('originalName') + ) { + $originalName = $type->getAttribute('originalName'); + + if ( + $originalName instanceof Node\Name + && 'null' === $originalName->toLowerString() + ) { + return true; + } + } + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php new file mode 100644 index 0000000..937bf0e --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/FinalRule.php @@ -0,0 +1,162 @@ + + */ +final class FinalRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $whitelistedAnnotations = [ + 'Entity', + 'ORM\Entity', + 'ORM\Mapping\Entity', + ]; + + /** + * @var list + */ + private static array $whitelistedAttributes = [ + ORM\Mapping\Entity::class, + ]; + private bool $allowAbstractClasses; + + /** + * @var list + */ + private array $classesNotRequiredToBeAbstractOrFinal; + private string $errorMessageTemplate = 'Class %s is not final.'; + + /** + * @param list $classesNotRequiredToBeAbstractOrFinal + */ + public function __construct( + bool $allowAbstractClasses, + array $classesNotRequiredToBeAbstractOrFinal + ) { + $this->allowAbstractClasses = $allowAbstractClasses; + $this->classesNotRequiredToBeAbstractOrFinal = \array_map(static function (string $classNotRequiredToBeAbstractOrFinal): string { + return $classNotRequiredToBeAbstractOrFinal; + }, $classesNotRequiredToBeAbstractOrFinal); + + if ($allowAbstractClasses) { + $this->errorMessageTemplate = 'Class %s is neither abstract nor final.'; + } + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!isset($node->namespacedName)) { + return []; + } + + if (\in_array($node->namespacedName->toString(), $this->classesNotRequiredToBeAbstractOrFinal, true)) { + return []; + } + + if ( + $this->allowAbstractClasses + && $node->isAbstract() + ) { + return []; + } + + if ($node->isFinal()) { + return []; + } + + if ($this->hasWhitelistedAnnotation($node)) { + return []; + } + + if ($this->hasWhitelistedAttribute($node)) { + return []; + } + + $message = \sprintf( + $this->errorMessageTemplate, + $node->namespacedName->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::final()->toString()) + ->build(), + ]; + } + + /** + * This method is inspired by the work on PhpCsFixer\Fixer\ClassNotation\FinalClassFixer and + * PhpCsFixer\Fixer\ClassNotation\FinalInternalClassFixer contributed by Dariusz Rumiński, Filippo Tessarotto, and + * Spacepossum for friendsofphp/php-cs-fixer. + * + * @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalClassFixer.php + * @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.15/src/Fixer/ClassNotation/FinalInternalClassFixer.php + * @see https://github.com/keradus + * @see https://github.com/SpacePossum + * @see https://github.com/Slamdunk + */ + private function hasWhitelistedAnnotation(Node\Stmt\Class_ $node): bool + { + $docComment = $node->getDocComment(); + + if (!$docComment instanceof Comment\Doc) { + return false; + } + + $reformattedComment = $docComment->getReformattedText(); + + if (\is_int(\preg_match_all('/@(\S+)(?=\s|$)/', $reformattedComment, $matches))) { + foreach ($matches[1] as $annotation) { + foreach (self::$whitelistedAnnotations as $whitelistedAnnotation) { + if (0 === \mb_strpos($annotation, $whitelistedAnnotation)) { + return true; + } + } + } + } + + return false; + } + + private function hasWhitelistedAttribute(Node\Stmt\Class_ $node): bool + { + foreach ($node->attrGroups as $attributeGroup) { + foreach ($attributeGroup->attrs as $attribute) { + if (\in_array($attribute->name->toString(), self::$whitelistedAttributes, true)) { + return true; + } + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php new file mode 100644 index 0000000..93ecf7c --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/NoExtendsRule.php @@ -0,0 +1,98 @@ + + */ +final class NoExtendsRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $defaultClassesAllowedToBeExtended = [ + 'PHPUnit\\Framework\\TestCase', + ]; + + /** + * @var list + */ + private array $classesAllowedToBeExtended; + + /** + * @param list $classesAllowedToBeExtended + */ + public function __construct(array $classesAllowedToBeExtended) + { + $this->classesAllowedToBeExtended = \array_values(\array_unique(\array_merge( + self::$defaultClassesAllowedToBeExtended, + \array_map(static function (string $classAllowedToBeExtended): string { + /** @var class-string $classAllowedToBeExtended */ + return $classAllowedToBeExtended; + }, $classesAllowedToBeExtended), + ))); + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$node->extends instanceof Node\Name) { + return []; + } + + $extendedClassName = $node->extends->toString(); + + if (\in_array($extendedClassName, $this->classesAllowedToBeExtended, true)) { + return []; + } + + if (!isset($node->namespacedName)) { + $message = \sprintf( + 'Anonymous class is not allowed to extend "%s".', + $extendedClassName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noExtends()->toString()) + ->build(), + ]; + } + + $extendingClassName = $node->namespacedName->toString(); + + $message = \sprintf( + 'Class "%s" is not allowed to extend "%s".', + $extendingClassName, + $extendedClassName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noExtends()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php new file mode 100644 index 0000000..76e96c5 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Classes/PHPUnit/Framework/TestCaseWithSuffixRule.php @@ -0,0 +1,95 @@ + + */ +final class TestCaseWithSuffixRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $phpunitTestCaseClassNames = [ + 'PHPUnit\Framework\TestCase', + ]; + private Reflection\ReflectionProvider $reflectionProvider; + + public function __construct(Reflection\ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if ($node->isAbstract()) { + return []; + } + + if (!$node->extends instanceof Node\Name) { + return []; + } + + if (!isset($node->namespacedName)) { + return []; + } + + $fullyQualifiedClassName = $node->namespacedName->toString(); + + $classReflection = $this->reflectionProvider->getClass($fullyQualifiedClassName); + + $extendedPhpunitTestCaseClassName = ''; + + foreach (self::$phpunitTestCaseClassNames as $phpunitTestCaseClassName) { + if ($classReflection->isSubclassOf($phpunitTestCaseClassName)) { + $extendedPhpunitTestCaseClassName = $phpunitTestCaseClassName; + + break; + } + } + + if ('' === $extendedPhpunitTestCaseClassName) { + return []; + } + + if (1 === \preg_match('/Test$/', $fullyQualifiedClassName)) { + return []; + } + + $message = \sprintf( + 'Class %s extends %s, is concrete, but does not have a Test suffix.', + $fullyQualifiedClassName, + $extendedPhpunitTestCaseClassName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::testCaseWithSuffix()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php new file mode 100644 index 0000000..6435157 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoNullableReturnTypeDeclarationRule.php @@ -0,0 +1,53 @@ + + */ +final class NoNullableReturnTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) { + return []; + } + + return [ + Rules\RuleErrorBuilder::message('Closure has a nullable return type declaration.') + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php new file mode 100644 index 0000000..2c47cb7 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php @@ -0,0 +1,64 @@ + + */ +final class NoParameterPassedByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return $parameter->byRef; + })); + + if (0 === \count($parametersPassedByReference)) { + return []; + } + + return \array_map(static function (Node\Param $parameterPassedByReference): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Closure has parameter $%s that is passed by reference.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString()) + ->build(); + }, $parametersPassedByReference); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php new file mode 100644 index 0000000..94bd146 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullDefaultValueRule.php @@ -0,0 +1,72 @@ + + */ +final class NoParameterWithNullDefaultValueRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->hasNullDefaultValue($parameter); + })); + + if (0 === \count($parametersWithNullDefaultValue)) { + return []; + } + + return \array_map(static function (Node\Param $parameterWithNullDefaultValue): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Closure has parameter $%s with null as default value.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php new file mode 100644 index 0000000..3cff6ee --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterWithNullableTypeDeclarationRule.php @@ -0,0 +1,72 @@ + + */ +final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->isNullableTypeDeclaration($parameter->type); + })); + + if (0 === \count($parametersWithNullableTypeDeclaration)) { + return []; + } + + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Closure has parameter $%s with a nullable type declaration.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php new file mode 100644 index 0000000..3fcbb10 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/ErrorIdentifier.php @@ -0,0 +1,130 @@ +value = $value; + } + + public static function declareStrictTypes(): self + { + return new self('declareStrictTypes'); + } + + public static function final(): self + { + return new self('final'); + } + + public static function finalInAbstractClass(): self + { + return new self('finalInAbstractClass'); + } + + public static function noCompact(): self + { + return new self('noCompact'); + } + + public static function noConstructorParameterWithDefaultValue(): self + { + return new self('noConstructorParameterWithDefaultValue'); + } + + public static function noAssignByReference(): self + { + return new self('noAssignByReference'); + } + + public static function noErrorSuppression(): self + { + return new self('noErrorSuppression'); + } + + public static function noEval(): self + { + return new self('noEval'); + } + + public static function noExtends(): self + { + return new self('noExtends'); + } + + public static function noIsset(): self + { + return new self('noIsset'); + } + + public static function noParameterPassedByReference(): self + { + return new self('noParameterPassedByReference'); + } + + public static function noParameterWithContainerTypeDeclaration(): self + { + return new self('noParameterWithContainerTypeDeclaration'); + } + + public static function noParameterWithNullDefaultValue(): self + { + return new self('noParameterWithNullDefaultValue'); + } + + public static function noParameterWithNullableTypeDeclaration(): self + { + return new self('noParameterWithNullableTypeDeclaration'); + } + + public static function noNullableReturnTypeDeclaration(): self + { + return new self('noNullableReturnTypeDeclaration'); + } + + public static function noReturnByReference(): self + { + return new self('noReturnByReference'); + } + + public static function noSwitch(): self + { + return new self('noSwitch'); + } + + public static function privateInFinalClass(): self + { + return new self('privateInFinalClass'); + } + + public static function testCaseWithSuffix(): self + { + return new self('testCaseWithSuffix'); + } + + public function toString(): string + { + return \sprintf( + 'ergebnis.%s', + $this->value, + ); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php new file mode 100644 index 0000000..ad8cc1e --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php @@ -0,0 +1,41 @@ + + */ +final class NoAssignByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\AssignRef::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Assign by reference should not be used.') + ->identifier(ErrorIdentifier::noAssignByReference()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php new file mode 100644 index 0000000..cd12ffe --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php @@ -0,0 +1,49 @@ + + */ +final class NoCompactRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$node->name instanceof Node\Name) { + return []; + } + + if ('compact' !== \mb_strtolower($scope->resolveName($node->name))) { + return []; + } + + return [ + Rules\RuleErrorBuilder::message('Function compact() should not be used.') + ->identifier(ErrorIdentifier::noCompact()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php new file mode 100644 index 0000000..49c0939 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php @@ -0,0 +1,41 @@ + + */ +final class NoErrorSuppressionRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\ErrorSuppress::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Error suppression via "@" should not be used.') + ->identifier(ErrorIdentifier::noErrorSuppression()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php new file mode 100644 index 0000000..b542057 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoEvalRule.php @@ -0,0 +1,41 @@ + + */ +final class NoEvalRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\Eval_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Language construct eval() should not be used.') + ->identifier(ErrorIdentifier::noEval()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php new file mode 100644 index 0000000..f1443d6 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php @@ -0,0 +1,41 @@ + + */ +final class NoIssetRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Expr\Isset_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Language construct isset() should not be used.') + ->identifier(ErrorIdentifier::noIsset()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php new file mode 100644 index 0000000..5f9ec11 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php @@ -0,0 +1,70 @@ + + */ +final class DeclareStrictTypesRule implements Rules\Rule +{ + public function getNodeType(): string + { + return FileNode::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + $nodes = $node->getNodes(); + + if (0 === \count($nodes)) { + return []; + } + + $firstNode = \array_shift($nodes); + + if ( + $firstNode instanceof Node\Stmt\InlineHTML + && 2 === $firstNode->getEndLine() + && 0 === \mb_strpos($firstNode->value, '#!') + ) { + $firstNode = \array_shift($nodes); + } + + if ($firstNode instanceof Node\Stmt\Declare_) { + foreach ($firstNode->declares as $declare) { + if ( + 'strict_types' === $declare->key->toLowerString() + && $declare->value instanceof Node\Scalar\LNumber + && 1 === $declare->value->value + ) { + return []; + } + } + } + + return [ + Rules\RuleErrorBuilder::message('File is missing a "declare(strict_types=1)" declaration.') + ->identifier(ErrorIdentifier::declareStrictTypes()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php new file mode 100644 index 0000000..06da4f7 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoNullableReturnTypeDeclarationRule.php @@ -0,0 +1,62 @@ + + */ +final class NoNullableReturnTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!isset($node->namespacedName)) { + return []; + } + + if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) { + return []; + } + + $message = \sprintf( + 'Function %s() has a nullable return type declaration.', + $node->namespacedName->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php new file mode 100644 index 0000000..e461aa1 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php @@ -0,0 +1,67 @@ + + */ +final class NoParameterPassedByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return $parameter->byRef; + })); + + if (0 === \count($parametersPassedByReference)) { + return []; + } + + $functionName = $node->namespacedName; + + return \array_map(static function (Node\Param $parameterPassedByReference) use ($functionName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Function %s() has parameter $%s that is passed by reference.', + $functionName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersPassedByReference); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php new file mode 100644 index 0000000..fe82a4e --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullDefaultValueRule.php @@ -0,0 +1,75 @@ + + */ +final class NoParameterWithNullDefaultValueRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->hasNullDefaultValue($parameter); + })); + + if (0 === \count($parametersWithNullDefaultValue)) { + return []; + } + + $functionName = $node->namespacedName; + + return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($functionName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Function %s() has parameter $%s with null as default value.', + $functionName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php new file mode 100644 index 0000000..9027553 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterWithNullableTypeDeclarationRule.php @@ -0,0 +1,75 @@ + + */ +final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->isNullableTypeDeclaration($parameter->type); + })); + + if (0 === \count($parametersWithNullableTypeDeclaration)) { + return []; + } + + $functionName = $node->namespacedName; + + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($functionName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Function %s() has parameter $%s with a nullable type declaration.', + $functionName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php new file mode 100644 index 0000000..0efb700 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php @@ -0,0 +1,50 @@ + + */ +final class NoReturnByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\Function_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (false === $node->byRef) { + return []; + } + + $message = \sprintf( + 'Function %s() returns by reference.', + $node->namespacedName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noReturnByReference()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php new file mode 100644 index 0000000..89fc226 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php @@ -0,0 +1,123 @@ + + */ +final class FinalInAbstractClassRule implements Rules\Rule +{ + private const DOCTRINE_ATTRIBUTE_NAMES = [ + 'Doctrine\\ORM\\Mapping\\Embeddable', + 'Doctrine\\ORM\\Mapping\\Entity', + ]; + private const DOCTRINE_TAG_NAMES = [ + '@ORM\\Mapping\\Embeddable', + '@ORM\\Embeddable', + '@Embeddable', + '@ORM\\Mapping\\Entity', + '@ORM\\Entity', + '@Entity', + ]; + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + /** @var Reflection\ClassReflection $containingClass */ + $containingClass = $scope->getClassReflection(); + + if (self::isDoctrineEntity($containingClass)) { + return []; + } + + if (!$containingClass->isAbstract()) { + return []; + } + + if ($containingClass->isInterface()) { + return []; + } + + if ($node->isAbstract()) { + return []; + } + + if ($node->isFinal()) { + return []; + } + + if ($node->isPrivate()) { + return []; + } + + if ('__construct' === $node->name->name) { + return []; + } + + $message = \sprintf( + 'Method %s::%s() is not final, but since the containing class is abstract, it should be.', + $containingClass->getName(), + $node->name->toString(), + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::finalInAbstractClass()->toString()) + ->build(), + ]; + } + + private static function isDoctrineEntity(Reflection\ClassReflection $containingClass): bool + { + $attributes = $containingClass->getNativeReflection()->getAttributes(); + + foreach ($attributes as $attribute) { + if (\in_array($attribute->getName(), self::DOCTRINE_ATTRIBUTE_NAMES, true)) { + return true; + } + } + + $resolvedPhpDocBlock = $containingClass->getResolvedPhpDoc(); + + if ($resolvedPhpDocBlock instanceof PhpDoc\ResolvedPhpDocBlock) { + foreach ($resolvedPhpDocBlock->getPhpDocNodes() as $phpDocNode) { + foreach ($phpDocNode->children as $child) { + if (!$child instanceof PhpDocParser\Ast\PhpDoc\PhpDocTagNode) { + continue; + } + + if (\in_array($child->name, self::DOCTRINE_TAG_NAMES, true)) { + return true; + } + } + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php new file mode 100644 index 0000000..e8b7e34 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoConstructorParameterWithDefaultValueRule.php @@ -0,0 +1,99 @@ + + */ +final class NoConstructorParameterWithDefaultValueRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if ('__construct' !== $node->name->toLowerString()) { + return []; + } + + if (0 === \count($node->params)) { + return []; + } + + $parametersWithDefaultValue = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return self::hasDefaultValue($parameter); + })); + + if (0 === \count($parametersWithDefaultValue)) { + return []; + } + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterWithDefaultValue): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Constructor in anonymous class has parameter $%s with default value.', + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noConstructorParameterWithDefaultValue()->toString()) + ->build(); + }, $parametersWithDefaultValue); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterWithDefaultValue) use ($className): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Constructor in %s has parameter $%s with default value.', + $className, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noConstructorParameterWithDefaultValue()->toString()) + ->build(); + }, $parametersWithDefaultValue); + } + + private static function hasDefaultValue(Node\Param $parameter): bool + { + return null !== $parameter->default; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php new file mode 100644 index 0000000..e0e84c9 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php @@ -0,0 +1,76 @@ + + */ +final class NoNullableReturnTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (!$this->analyzer->isNullableTypeDeclaration($node->getReturnType())) { + return []; + } + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class has a nullable return type declaration.', + $node->name->name, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } + + $message = \sprintf( + 'Method %s::%s() has a nullable return type declaration.', + $classReflection->getName(), + $node->name->name, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noNullableReturnTypeDeclaration()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php new file mode 100644 index 0000000..4c90f69 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php @@ -0,0 +1,94 @@ + + */ +final class NoParameterPassedByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersExplicitlyPassedByReference = \array_values(\array_filter($node->params, static function (Node\Param $parameter): bool { + return $parameter->byRef; + })); + + if (0 === \count($parametersExplicitlyPassedByReference)) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterExplicitlyPassedByReference) use ($methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterExplicitlyPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s() in anonymous class has parameter $%s that is passed by reference.', + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString()) + ->build(); + }, $parametersExplicitlyPassedByReference); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterExplicitlyPassedByReference) use ($className, $methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterExplicitlyPassedByReference->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s::%s() has parameter $%s that is passed by reference.', + $className, + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterPassedByReference()->toString()) + ->build(); + }, $parametersExplicitlyPassedByReference); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php new file mode 100644 index 0000000..f64a831 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithContainerTypeDeclarationRule.php @@ -0,0 +1,179 @@ + + */ +final class NoParameterWithContainerTypeDeclarationRule implements Rules\Rule +{ + private Reflection\ReflectionProvider $reflectionProvider; + + /** + * @var list + */ + private array $interfacesImplementedByContainers; + + /** + * @var list + */ + private array $methodsAllowedToUseContainerTypeDeclarations; + + /** + * @param list $interfacesImplementedByContainers + * @param list $methodsAllowedToUseContainerTypeDeclarations + */ + public function __construct( + Reflection\ReflectionProvider $reflectionProvider, + array $interfacesImplementedByContainers, + array $methodsAllowedToUseContainerTypeDeclarations + ) { + $this->reflectionProvider = $reflectionProvider; + $this->interfacesImplementedByContainers = \array_values(\array_filter( + \array_map(static function (string $interfaceImplementedByContainers): string { + return $interfaceImplementedByContainers; + }, $interfacesImplementedByContainers), + static function (string $interfaceImplementedByContainer): bool { + return \interface_exists($interfaceImplementedByContainer); + }, + )); + $this->methodsAllowedToUseContainerTypeDeclarations = $methodsAllowedToUseContainerTypeDeclarations; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($this->interfacesImplementedByContainers)) { + return []; + } + + if (0 === \count($node->params)) { + return []; + } + + $methodName = $node->name->toString(); + + if (\in_array($methodName, $this->methodsAllowedToUseContainerTypeDeclarations, true)) { + return []; + } + + /** @var Reflection\ClassReflection $containingClass */ + $containingClass = $scope->getClassReflection(); + + return \array_values(\array_reduce( + $node->params, + function (array $errors, Node\Param $node) use ($scope, $containingClass, $methodName): array { + $type = $node->type; + + if (!$type instanceof Node\Name) { + return $errors; + } + + /** @var Node\Expr\Variable $variable */ + $variable = $node->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $classUsedInTypeDeclaration = $this->reflectionProvider->getClass($scope->resolveName($type)); + + if ($classUsedInTypeDeclaration->isInterface()) { + foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) { + if ($classUsedInTypeDeclaration->getName() === $interfaceImplementedByContainer) { + $errors[] = self::createError( + $containingClass, + $methodName, + $parameterName, + $classUsedInTypeDeclaration, + ); + + return $errors; + } + + if ($classUsedInTypeDeclaration->getNativeReflection()->isSubclassOf($interfaceImplementedByContainer)) { + $errors[] = self::createError( + $containingClass, + $methodName, + $parameterName, + $classUsedInTypeDeclaration, + ); + + return $errors; + } + } + } + + foreach ($this->interfacesImplementedByContainers as $interfaceImplementedByContainer) { + if ($classUsedInTypeDeclaration->getNativeReflection()->implementsInterface($interfaceImplementedByContainer)) { + $errors[] = self::createError( + $containingClass, + $methodName, + $parameterName, + $classUsedInTypeDeclaration, + ); + + return $errors; + } + } + + return $errors; + }, + [], + )); + } + + private static function createError( + Reflection\ClassReflection $classReflection, + string $methodName, + string $parameterName, + Reflection\ClassReflection $classUsedInTypeDeclaration + ): Rules\RuleError { + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class has a parameter $%s with a type declaration of %s, but containers should not be injected.', + $methodName, + $parameterName, + $classUsedInTypeDeclaration->getName(), + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithContainerTypeDeclaration()->toString()) + ->build(); + } + + $message = \sprintf( + 'Method %s::%s() has a parameter $%s with a type declaration of %s, but containers should not be injected.', + $classReflection->getName(), + $methodName, + $parameterName, + $classUsedInTypeDeclaration->getName(), + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithContainerTypeDeclaration()->toString()) + ->build(); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php new file mode 100644 index 0000000..9888974 --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php @@ -0,0 +1,102 @@ + + */ +final class NoParameterWithNullDefaultValueRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullDefaultValue = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->hasNullDefaultValue($parameter); + })); + + if (0 === \count($parametersWithNullDefaultValue)) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s() in anonymous class has parameter $%s with null as default value.', + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterWithNullDefaultValue) use ($className, $methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullDefaultValue->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s::%s() has parameter $%s with null as default value.', + $className, + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullDefaultValue()->toString()) + ->build(); + }, $parametersWithNullDefaultValue); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php new file mode 100644 index 0000000..632cd7b --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullableTypeDeclarationRule.php @@ -0,0 +1,102 @@ + + */ +final class NoParameterWithNullableTypeDeclarationRule implements Rules\Rule +{ + private Analyzer $analyzer; + + public function __construct(Analyzer $analyzer) + { + $this->analyzer = $analyzer; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (0 === \count($node->params)) { + return []; + } + + $parametersWithNullableTypeDeclaration = \array_values(\array_filter($node->params, function (Node\Param $parameter): bool { + return $this->analyzer->isNullableTypeDeclaration($parameter->type); + })); + + if (0 === \count($parametersWithNullableTypeDeclaration)) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s() in anonymous class has parameter $%s with a nullable type declaration.', + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } + + $className = $classReflection->getName(); + + return \array_map(static function (Node\Param $parameterWithNullableTypeDeclaration) use ($className, $methodName): Rules\RuleError { + /** @var Node\Expr\Variable $variable */ + $variable = $parameterWithNullableTypeDeclaration->var; + + /** @var string $parameterName */ + $parameterName = $variable->name; + + $message = \sprintf( + 'Method %s::%s() has parameter $%s with a nullable type declaration.', + $className, + $methodName, + $parameterName, + ); + + return Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noParameterWithNullableTypeDeclaration()->toString()) + ->build(); + }, $parametersWithNullableTypeDeclaration); + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php new file mode 100644 index 0000000..a1bf87f --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php @@ -0,0 +1,72 @@ + + */ +final class NoReturnByReferenceRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + if (false === $node->byRef) { + return []; + } + + $methodName = $node->name->toString(); + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class returns by reference.', + $methodName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noReturnByReference()->toString()) + ->build(), + ]; + } + + $className = $classReflection->getName(); + + $message = \sprintf( + 'Method %s::%s() returns by reference.', + $className, + $methodName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::noReturnByReference()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php new file mode 100644 index 0000000..9aa2c3c --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php @@ -0,0 +1,198 @@ + + */ +final class PrivateInFinalClassRule implements Rules\Rule +{ + /** + * @var list + */ + private static array $whitelistedAnnotations = [ + '@after', + '@before', + '@postCondition', + '@preCondition', + ]; + + /** + * @var list + */ + private static array $whitelistedAttributes = [ + Framework\Attributes\After::class, + Framework\Attributes\Before::class, + Framework\Attributes\PostCondition::class, + Framework\Attributes\PreCondition::class, + ]; + private Type\FileTypeMapper $fileTypeMapper; + + public function __construct(Type\FileTypeMapper $fileTypeMapper) + { + $this->fileTypeMapper = $fileTypeMapper; + } + + public function getNodeType(): string + { + return Node\Stmt\ClassMethod::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + /** @var Reflection\ClassReflection $containingClass */ + $containingClass = $scope->getClassReflection(); + + if (!$containingClass->isFinal()) { + return []; + } + + if ($node->isPublic()) { + return []; + } + + if ($node->isPrivate()) { + return []; + } + + if ($this->hasWhitelistedAnnotation($node, $containingClass)) { + return []; + } + + if (self::hasWhitelistedAttribute($node)) { + return []; + } + + $methodName = $node->name->toString(); + + if (self::isDeclaredByParentClass($containingClass, $methodName)) { + return []; + } + + if (self::isDeclaredByTrait($containingClass, $methodName)) { + return []; + } + + /** @var Reflection\ClassReflection $classReflection */ + $classReflection = $scope->getClassReflection(); + + if ($classReflection->isAnonymous()) { + $message = \sprintf( + 'Method %s() in anonymous class is protected, but since the containing class is final, it can be private.', + $node->name->name, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::privateInFinalClass()->toString()) + ->build(), + ]; + } + + $message = \sprintf( + 'Method %s::%s() is protected, but since the containing class is final, it can be private.', + $containingClass->getName(), + $methodName, + ); + + return [ + Rules\RuleErrorBuilder::message($message) + ->identifier(ErrorIdentifier::privateInFinalClass()->toString()) + ->build(), + ]; + } + + private function hasWhitelistedAnnotation( + Node\Stmt\ClassMethod $node, + Reflection\ClassReflection $containingClass + ): bool { + $docComment = $node->getDocComment(); + + if (!$docComment instanceof Comment\Doc) { + return false; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + null, + $containingClass->getName(), + null, + null, + $docComment->getText(), + ); + + foreach ($resolvedPhpDoc->getPhpDocNodes() as $phpDocNode) { + foreach ($phpDocNode->getTags() as $tag) { + if (\in_array($tag->name, self::$whitelistedAnnotations, true)) { + return true; + } + } + } + + return false; + } + + private static function hasWhitelistedAttribute(Node\Stmt\ClassMethod $node): bool + { + foreach ($node->attrGroups as $attributeGroup) { + foreach ($attributeGroup->attrs as $attribute) { + if (\in_array($attribute->name->toString(), self::$whitelistedAttributes, true)) { + return true; + } + } + } + + return false; + } + + private static function isDeclaredByParentClass( + Reflection\ClassReflection $containingClass, + string $methodName + ): bool { + $parentClass = $containingClass->getNativeReflection()->getParentClass(); + + if (!$parentClass instanceof \ReflectionClass) { + return false; + } + + if (!$parentClass->hasMethod($methodName)) { + return false; + } + + return true; + } + + private static function isDeclaredByTrait( + Reflection\ClassReflection $containingClass, + string $methodName + ): bool { + foreach ($containingClass->getTraits() as $trait) { + if ($trait->hasMethod($methodName)) { + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php new file mode 100644 index 0000000..127e5ee --- /dev/null +++ b/tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php @@ -0,0 +1,41 @@ + + */ +final class NoSwitchRule implements Rules\Rule +{ + public function getNodeType(): string + { + return Node\Stmt\Switch_::class; + } + + public function processNode( + Node $node, + Analyser\Scope $scope + ): array { + return [ + Rules\RuleErrorBuilder::message('Control structures using switch should not be used.') + ->identifier(ErrorIdentifier::noSwitch()->toString()) + ->build(), + ]; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php b/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php new file mode 100644 index 0000000..25851af --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/.phpstorm.meta.php @@ -0,0 +1,13 @@ + +✅ [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
+✅ [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …
+✅ [Finder](https://doc.nette.org/utils/finder) - finds files and directories
+✅ [Floats](https://doc.nette.org/utils/floats) - floating point numbers
+✅ [Helper Functions](https://doc.nette.org/utils/helpers)
+✅ [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
+✅ [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
+✅ [Iterables](https://doc.nette.org/utils/iterables)
+✅ [JSON](https://doc.nette.org/utils/json) - encoding and decoding
+✅ [Generating Random Strings](https://doc.nette.org/utils/random)
+✅ [Paginator](https://doc.nette.org/utils/paginator) - pagination math
+✅ [PHP Reflection](https://doc.nette.org/utils/reflection)
+✅ [Strings](https://doc.nette.org/utils/strings) - useful text functions
+✅ [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements
+✅ [Type](https://doc.nette.org/utils/type) - PHP data type
+✅ [Validation](https://doc.nette.org/utils/validators) - validate inputs
+ +  + +Installation +------------ + +The recommended way to install is via Composer: + +``` +composer require nette/utils +``` + +Nette Utils 4.0 is compatible with PHP 8.0 to 8.4. + +  + +[Support Me](https://github.com/sponsors/dg) +-------------------------------------------- + +Do you like Nette Utils? Are you looking forward to the new features? + +[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg) + +Thank you! diff --git a/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php b/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php new file mode 100644 index 0000000..d749d4e --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/HtmlStringable.php @@ -0,0 +1,22 @@ +counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0); + } + + + /** + * Is the current element the last one? + */ + public function isLast(?int $gridWidth = null): bool + { + return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0); + } + + + /** + * Is the iterator empty? + */ + public function isEmpty(): bool + { + return $this->counter === 0; + } + + + /** + * Is the counter odd? + */ + public function isOdd(): bool + { + return $this->counter % 2 === 1; + } + + + /** + * Is the counter even? + */ + public function isEven(): bool + { + return $this->counter % 2 === 0; + } + + + /** + * Returns the counter. + */ + public function getCounter(): int + { + return $this->counter; + } + + + /** + * Returns the count of elements. + */ + public function count(): int + { + $inner = $this->getInnerIterator(); + if ($inner instanceof \Countable) { + return $inner->count(); + + } else { + throw new Nette\NotSupportedException('Iterator is not countable.'); + } + } + + + /** + * Forwards to the next element. + */ + public function next(): void + { + parent::next(); + if (parent::valid()) { + $this->counter++; + } + } + + + /** + * Rewinds the Iterator. + */ + public function rewind(): void + { + parent::rewind(); + $this->counter = parent::valid() ? 1 : 0; + } + + + /** + * Returns the next key. + */ + public function getNextKey(): mixed + { + return $this->getInnerIterator()->key(); + } + + + /** + * Returns the next element. + */ + public function getNextValue(): mixed + { + return $this->getInnerIterator()->current(); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php b/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php new file mode 100644 index 0000000..284da29 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Iterators/Mapper.php @@ -0,0 +1,33 @@ +callback = $callback; + } + + + public function current(): mixed + { + return ($this->callback)(parent::current(), parent::key()); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/SmartObject.php b/tools/.phpstan/vendor/nette/utils/src/SmartObject.php new file mode 100644 index 0000000..3b2203f --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/SmartObject.php @@ -0,0 +1,140 @@ +$name ?? null; + if (is_iterable($handlers)) { + foreach ($handlers as $handler) { + $handler(...$args); + } + } elseif ($handlers !== null) { + throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . get_debug_type($handlers) . ' given.'); + } + + return null; + } + + ObjectHelpers::strictCall($class, $name); + } + + + /** + * @throws MemberAccessException + */ + public static function __callStatic(string $name, array $args) + { + ObjectHelpers::strictStaticCall(static::class, $name); + } + + + /** + * @return mixed + * @throws MemberAccessException if the property is not defined. + */ + public function &__get(string $name) + { + $class = static::class; + + if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter + if (!($prop & 0b0001)) { + throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); + } + + $m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } + + if ($prop & 0b0100) { // return by reference + return $this->$m(); + } else { + $val = $this->$m(); + return $val; + } + } else { + ObjectHelpers::strictGet($class, $name); + } + } + + + /** + * @throws MemberAccessException if the property is not defined or is read-only + */ + public function __set(string $name, mixed $value): void + { + $class = static::class; + + if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property + $this->$name = $value; + + } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter + if (!($prop & 0b1000)) { + throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); + } + + $m = 'set' . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } + + $this->$m($value); + + } else { + ObjectHelpers::strictSet($class, $name); + } + } + + + /** + * @throws MemberAccessException + */ + public function __unset(string $name): void + { + $class = static::class; + if (!ObjectHelpers::hasProperty($class, $name)) { + throw new MemberAccessException("Cannot unset the property $class::\$$name."); + } + } + + + public function __isset(string $name): bool + { + return isset(ObjectHelpers::getMagicProperties(static::class)[$name]); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/StaticClass.php b/tools/.phpstan/vendor/nette/utils/src/StaticClass.php new file mode 100644 index 0000000..b1d8486 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/StaticClass.php @@ -0,0 +1,34 @@ + + * @implements \ArrayAccess + */ +class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate +{ + /** + * Transforms array to ArrayHash. + * @param array $array + */ + public static function from(array $array, bool $recursive = true): static + { + $obj = new static; + foreach ($array as $key => $value) { + $obj->$key = $recursive && is_array($value) + ? static::from($value) + : $value; + } + + return $obj; + } + + + /** + * Returns an iterator over all items. + * @return \Iterator + */ + public function &getIterator(): \Iterator + { + foreach ((array) $this as $key => $foo) { + yield $key => $this->$key; + } + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count((array) $this); + } + + + /** + * Replaces or appends a item. + * @param array-key $key + * @param T $value + */ + public function offsetSet($key, $value): void + { + if (!is_scalar($key)) { // prevents null + throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', get_debug_type($key))); + } + + $this->$key = $value; + } + + + /** + * Returns a item. + * @param array-key $key + * @return T + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->$key; + } + + + /** + * Determines whether a item exists. + * @param array-key $key + */ + public function offsetExists($key): bool + { + return isset($this->$key); + } + + + /** + * Removes the element from this list. + * @param array-key $key + */ + public function offsetUnset($key): void + { + unset($this->$key); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php new file mode 100644 index 0000000..a402f9b --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ArrayList.php @@ -0,0 +1,136 @@ + + * @implements \ArrayAccess + */ +class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate +{ + use Nette\SmartObject; + + private array $list = []; + + + /** + * Transforms array to ArrayList. + * @param list $array + */ + public static function from(array $array): static + { + if (!Arrays::isList($array)) { + throw new Nette\InvalidArgumentException('Array is not valid list.'); + } + + $obj = new static; + $obj->list = $array; + return $obj; + } + + + /** + * Returns an iterator over all items. + * @return \Iterator + */ + public function &getIterator(): \Iterator + { + foreach ($this->list as &$item) { + yield $item; + } + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count($this->list); + } + + + /** + * Replaces or appends a item. + * @param int|null $index + * @param T $value + * @throws Nette\OutOfRangeException + */ + public function offsetSet($index, $value): void + { + if ($index === null) { + $this->list[] = $value; + + } elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + + } else { + $this->list[$index] = $value; + } + } + + + /** + * Returns a item. + * @param int $index + * @return T + * @throws Nette\OutOfRangeException + */ + public function offsetGet($index): mixed + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + + return $this->list[$index]; + } + + + /** + * Determines whether a item exists. + * @param int $index + */ + public function offsetExists($index): bool + { + return is_int($index) && $index >= 0 && $index < count($this->list); + } + + + /** + * Removes the element at the specified position in this list. + * @param int $index + * @throws Nette\OutOfRangeException + */ + public function offsetUnset($index): void + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + + array_splice($this->list, $index, 1); + } + + + /** + * Prepends a item. + * @param T $value + */ + public function prepend(mixed $value): void + { + $first = array_slice($this->list, 0, 1); + $this->offsetSet(0, $value); + array_splice($this->list, 1, 0, $first); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php new file mode 100644 index 0000000..00a4a8c --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Arrays.php @@ -0,0 +1,553 @@ + $array + * @param array-key|array-key[] $key + * @param ?T $default + * @return ?T + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function get(array $array, string|int|array $key, mixed $default = null): mixed + { + foreach (is_array($key) ? $key : [$key] as $k) { + if (is_array($array) && array_key_exists($k, $array)) { + $array = $array[$k]; + } else { + if (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$k'."); + } + + return $default; + } + } + + return $array; + } + + + /** + * Returns reference to array item. If the index does not exist, new one is created with value null. + * @template T + * @param array $array + * @param array-key|array-key[] $key + * @return ?T + * @throws Nette\InvalidArgumentException if traversed item is not an array + */ + public static function &getRef(array &$array, string|int|array $key): mixed + { + foreach (is_array($key) ? $key : [$key] as $k) { + if (is_array($array) || $array === null) { + $array = &$array[$k]; + } else { + throw new Nette\InvalidArgumentException('Traversed item is not an array.'); + } + } + + return $array; + } + + + /** + * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as + * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains + * the value from the first array in the case of a key collision. + * @template T1 + * @template T2 + * @param array $array1 + * @param array $array2 + * @return array + */ + public static function mergeTree(array $array1, array $array2): array + { + $res = $array1 + $array2; + foreach (array_intersect_key($array1, $array2) as $k => $v) { + if (is_array($v) && is_array($array2[$k])) { + $res[$k] = self::mergeTree($v, $array2[$k]); + } + } + + return $res; + } + + + /** + * Returns zero-indexed position of given array key. Returns null if key is not found. + */ + public static function getKeyOffset(array $array, string|int $key): ?int + { + return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), strict: true)); + } + + + /** + * @deprecated use getKeyOffset() + */ + public static function searchKey(array $array, $key): ?int + { + return self::getKeyOffset($array, $key); + } + + + /** + * Tests an array for the presence of value. + */ + public static function contains(array $array, mixed $value): bool + { + return in_array($value, $array, true); + } + + + /** + * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?V + */ + public static function first(array $array, ?callable $predicate = null, ?callable $else = null): mixed + { + $key = self::firstKey($array, $predicate); + return $key === null + ? ($else ? $else() : null) + : $array[$key]; + } + + + /** + * Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?V + */ + public static function last(array $array, ?callable $predicate = null, ?callable $else = null): mixed + { + $key = self::lastKey($array, $predicate); + return $key === null + ? ($else ? $else() : null) + : $array[$key]; + } + + + /** + * Returns the key of first item (matching the specified predicate if given) or null if there is no such item. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?K + */ + public static function firstKey(array $array, ?callable $predicate = null): int|string|null + { + if (!$predicate) { + return array_key_first($array); + } + foreach ($array as $k => $v) { + if ($predicate($v, $k, $array)) { + return $k; + } + } + return null; + } + + + /** + * Returns the key of last item (matching the specified predicate if given) or null if there is no such item. + * @template K of int|string + * @template V + * @param array $array + * @param ?callable(V, K, array): bool $predicate + * @return ?K + */ + public static function lastKey(array $array, ?callable $predicate = null): int|string|null + { + return $predicate + ? self::firstKey(array_reverse($array, preserve_keys: true), $predicate) + : array_key_last($array); + } + + + /** + * Inserts the contents of the $inserted array into the $array immediately after the $key. + * If $key is null (or does not exist), it is inserted at the beginning. + */ + public static function insertBefore(array &$array, string|int|null $key, array $inserted): void + { + $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key); + $array = array_slice($array, 0, $offset, preserve_keys: true) + + $inserted + + array_slice($array, $offset, count($array), preserve_keys: true); + } + + + /** + * Inserts the contents of the $inserted array into the $array before the $key. + * If $key is null (or does not exist), it is inserted at the end. + */ + public static function insertAfter(array &$array, string|int|null $key, array $inserted): void + { + if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) { + $offset = count($array) - 1; + } + + $array = array_slice($array, 0, $offset + 1, preserve_keys: true) + + $inserted + + array_slice($array, $offset + 1, count($array), preserve_keys: true); + } + + + /** + * Renames key in array. + */ + public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool + { + $offset = self::getKeyOffset($array, $oldKey); + if ($offset === null) { + return false; + } + + $val = &$array[$oldKey]; + $keys = array_keys($array); + $keys[$offset] = $newKey; + $array = array_combine($keys, $array); + $array[$newKey] = &$val; + return true; + } + + + /** + * Returns only those array items, which matches a regular expression $pattern. + * @param string[] $array + * @return string[] + */ + public static function grep( + array $array, + #[Language('RegExp')] + string $pattern, + bool|int $invert = false, + ): array + { + $flags = $invert ? PREG_GREP_INVERT : 0; + return Strings::pcre('preg_grep', [$pattern, $array, $flags]); + } + + + /** + * Transforms multidimensional array to flat array. + */ + public static function flatten(array $array, bool $preserveKeys = false): array + { + $res = []; + $cb = $preserveKeys + ? function ($v, $k) use (&$res): void { $res[$k] = $v; } + : function ($v) use (&$res): void { $res[] = $v; }; + array_walk_recursive($array, $cb); + return $res; + } + + + /** + * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. + * @return ($value is list ? true : false) + */ + public static function isList(mixed $value): bool + { + return is_array($value) && (PHP_VERSION_ID < 80100 + ? !$value || array_keys($value) === range(0, count($value) - 1) + : array_is_list($value) + ); + } + + + /** + * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'. + * @param string|string[] $path + */ + public static function associate(array $array, $path): array|\stdClass + { + $parts = is_array($path) + ? $path + : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') { + throw new Nette\InvalidArgumentException("Invalid path '$path'."); + } + + $res = $parts[0] === '->' ? new \stdClass : []; + + foreach ($array as $rowOrig) { + $row = (array) $rowOrig; + $x = &$res; + + for ($i = 0; $i < count($parts); $i++) { + $part = $parts[$i]; + if ($part === '[]') { + $x = &$x[]; + + } elseif ($part === '=') { + if (isset($parts[++$i])) { + $x = $row[$parts[$i]]; + $row = null; + } + } elseif ($part === '->') { + if (isset($parts[++$i])) { + if ($x === null) { + $x = new \stdClass; + } + + $x = &$x->{$row[$parts[$i]]}; + } else { + $row = is_object($rowOrig) ? $rowOrig : (object) $row; + } + } elseif ($part !== '|') { + $x = &$x[(string) $row[$part]]; + } + } + + if ($x === null) { + $x = $row; + } + } + + return $res; + } + + + /** + * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling. + */ + public static function normalize(array $array, mixed $filling = null): array + { + $res = []; + foreach ($array as $k => $v) { + $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v; + } + + return $res; + } + + + /** + * Returns and removes the value of an item from an array. If it does not exist, it throws an exception, + * or returns $default, if provided. + * @template T + * @param array $array + * @param ?T $default + * @return ?T + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function pick(array &$array, string|int $key, mixed $default = null): mixed + { + if (array_key_exists($key, $array)) { + $value = $array[$key]; + unset($array[$key]); + return $value; + + } elseif (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$key'."); + + } else { + return $default; + } + } + + + /** + * Tests whether at least one element in the array passes the test implemented by the provided function. + * @template K of int|string + * @template V + * @param array $array + * @param callable(V, K, array): bool $predicate + */ + public static function some(iterable $array, callable $predicate): bool + { + foreach ($array as $k => $v) { + if ($predicate($v, $k, $array)) { + return true; + } + } + + return false; + } + + + /** + * Tests whether all elements in the array pass the test implemented by the provided function. + * @template K of int|string + * @template V + * @param array $array + * @param callable(V, K, array): bool $predicate + */ + public static function every(iterable $array, callable $predicate): bool + { + foreach ($array as $k => $v) { + if (!$predicate($v, $k, $array)) { + return false; + } + } + + return true; + } + + + /** + * Returns a new array containing all key-value pairs matching the given $predicate. + * @template K of int|string + * @template V + * @param array $array + * @param callable(V, K, array): bool $predicate + * @return array + */ + public static function filter(array $array, callable $predicate): array + { + $res = []; + foreach ($array as $k => $v) { + if ($predicate($v, $k, $array)) { + $res[$k] = $v; + } + } + return $res; + } + + + /** + * Returns an array containing the original keys and results of applying the given transform function to each element. + * @template K of int|string + * @template V + * @template R + * @param array $array + * @param callable(V, K, array): R $transformer + * @return array + */ + public static function map(iterable $array, callable $transformer): array + { + $res = []; + foreach ($array as $k => $v) { + $res[$k] = $transformer($v, $k, $array); + } + + return $res; + } + + + /** + * Returns an array containing new keys and values generated by applying the given transform function to each element. + * If the function returns null, the element is skipped. + * @template K of int|string + * @template V + * @template ResK of int|string + * @template ResV + * @param array $array + * @param callable(V, K, array): ?array{ResK, ResV} $transformer + * @return array + */ + public static function mapWithKeys(array $array, callable $transformer): array + { + $res = []; + foreach ($array as $k => $v) { + $pair = $transformer($v, $k, $array); + if ($pair) { + $res[$pair[0]] = $pair[1]; + } + } + + return $res; + } + + + /** + * Invokes all callbacks and returns array of results. + * @param callable[] $callbacks + */ + public static function invoke(iterable $callbacks, ...$args): array + { + $res = []; + foreach ($callbacks as $k => $cb) { + $res[$k] = $cb(...$args); + } + + return $res; + } + + + /** + * Invokes method on every object in an array and returns array of results. + * @param object[] $objects + */ + public static function invokeMethod(iterable $objects, string $method, ...$args): array + { + $res = []; + foreach ($objects as $k => $obj) { + $res[$k] = $obj->$method(...$args); + } + + return $res; + } + + + /** + * Copies the elements of the $array array to the $object object and then returns it. + * @template T of object + * @param T $object + * @return T + */ + public static function toObject(iterable $array, object $object): object + { + foreach ($array as $k => $v) { + $object->$k = $v; + } + + return $object; + } + + + /** + * Converts value to array key. + */ + public static function toKey(mixed $value): int|string + { + return key([$value => null]); + } + + + /** + * Returns copy of the $array where every item is converted to string + * and prefixed by $prefix and suffixed by $suffix. + * @param string[] $array + * @return string[] + */ + public static function wrap(array $array, string $prefix = '', string $suffix = ''): array + { + $res = []; + foreach ($array as $k => $v) { + $res[$k] = $prefix . $v . $suffix; + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php new file mode 100644 index 0000000..1777428 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Callback.php @@ -0,0 +1,137 @@ +getClosureScopeClass()?->name; + if (str_ends_with($r->name, '}')) { + return $closure; + + } elseif (($obj = $r->getClosureThis()) && $obj::class === $class) { + return [$obj, $r->name]; + + } elseif ($class) { + return [$class, $r->name]; + + } else { + return $r->name; + } + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php new file mode 100644 index 0000000..6ad6520 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/DateTime.php @@ -0,0 +1,140 @@ +format('Y-m-d H:i:s.u'), $time->getTimezone()); + + } elseif (is_numeric($time)) { + if ($time <= self::YEAR) { + $time += time(); + } + + return (new static)->setTimestamp((int) $time); + + } else { // textual or null + return new static((string) $time); + } + } + + + /** + * Creates DateTime object. + * @throws Nette\InvalidArgumentException if the date and time are not valid. + */ + public static function fromParts( + int $year, + int $month, + int $day, + int $hour = 0, + int $minute = 0, + float $second = 0.0, + ): static + { + $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second); + if ( + !checkdate($month, $day, $year) + || $hour < 0 + || $hour > 23 + || $minute < 0 + || $minute > 59 + || $second < 0 + || $second >= 60 + ) { + throw new Nette\InvalidArgumentException("Invalid date '$s'"); + } + + return new static($s); + } + + + /** + * Returns new DateTime object formatted according to the specified format. + */ + public static function createFromFormat( + string $format, + string $time, + string|\DateTimeZone|null $timezone = null, + ): static|false + { + if ($timezone === null) { + $timezone = new \DateTimeZone(date_default_timezone_get()); + + } elseif (is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } + + $date = parent::createFromFormat($format, $time, $timezone); + return $date ? static::from($date) : false; + } + + + /** + * Returns JSON representation in ISO 8601 (used by JavaScript). + */ + public function jsonSerialize(): string + { + return $this->format('c'); + } + + + /** + * Returns the date and time in the format 'Y-m-d H:i:s'. + */ + public function __toString(): string + { + return $this->format('Y-m-d H:i:s'); + } + + + /** + * You'd better use: (clone $dt)->modify(...) + */ + public function modifyClone(string $modify = ''): static + { + $dolly = clone $this; + return $modify ? $dolly->modify($modify) : $dolly; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php new file mode 100644 index 0000000..fb92d11 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileInfo.php @@ -0,0 +1,69 @@ +setInfoClass(static::class); + $this->relativePath = $relativePath; + } + + + /** + * Returns the relative directory path. + */ + public function getRelativePath(): string + { + return $this->relativePath; + } + + + /** + * Returns the relative path including file name. + */ + public function getRelativePathname(): string + { + return ($this->relativePath === '' ? '' : $this->relativePath . DIRECTORY_SEPARATOR) + . $this->getBasename(); + } + + + /** + * Returns the contents of the file. + * @throws Nette\IOException + */ + public function read(): string + { + return FileSystem::read($this->getPathname()); + } + + + /** + * Writes the contents to the file. + * @throws Nette\IOException + */ + public function write(string $content): void + { + FileSystem::write($this->getPathname(), $content); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php new file mode 100644 index 0000000..ab9a7e8 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/FileSystem.php @@ -0,0 +1,326 @@ +getPathname()); + } + + foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) { + if ($item->isDir()) { + static::createDir($target . '/' . $iterator->getSubPathName()); + } else { + static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName()); + } + } + } else { + static::createDir(dirname($target)); + if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to copy file '%s' to '%s'. %s", + self::normalizePath($origin), + self::normalizePath($target), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Opens file and returns resource. + * @return resource + * @throws Nette\IOException on error occurred + */ + public static function open(string $path, string $mode) + { + $f = @fopen($path, $mode); // @ is escalated to exception + if (!$f) { + throw new Nette\IOException(sprintf( + "Unable to open file '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + return $f; + } + + + /** + * Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first. + * @throws Nette\IOException on error occurred + */ + public static function delete(string $path): void + { + if (is_file($path) || is_link($path)) { + $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink'; + if (!@$func($path)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to delete '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::delete($item->getPathname()); + } + + if (!@rmdir($path)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to delete directory '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Renames or moves a file or a directory. Overwrites existing files and directories by default. + * @throws Nette\IOException on error occurred + * @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists + */ + public static function rename(string $origin, string $target, bool $overwrite = true): void + { + if (!$overwrite && file_exists($target)) { + throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target))); + + } elseif (!file_exists($origin)) { + throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin))); + + } else { + static::createDir(dirname($target)); + if (realpath($origin) !== realpath($target)) { + static::delete($target); + } + + if (!@rename($origin, $target)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to rename file or directory '%s' to '%s'. %s", + self::normalizePath($origin), + self::normalizePath($target), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Reads the content of a file. + * @throws Nette\IOException on error occurred + */ + public static function read(string $file): string + { + $content = @file_get_contents($file); // @ is escalated to exception + if ($content === false) { + throw new Nette\IOException(sprintf( + "Unable to read file '%s'. %s", + self::normalizePath($file), + Helpers::getLastError(), + )); + } + + return $content; + } + + + /** + * Reads the file content line by line. Because it reads continuously as we iterate over the lines, + * it is possible to read files larger than the available memory. + * @return \Generator + * @throws Nette\IOException on error occurred + */ + public static function readLines(string $file, bool $stripNewLines = true): \Generator + { + return (function ($f) use ($file, $stripNewLines) { + $counter = 0; + do { + $line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf( + "Unable to read file '%s'. %s", + self::normalizePath($file), + $error, + ))); + if ($line === false) { + fclose($f); + break; + } + if ($stripNewLines) { + $line = rtrim($line, "\r\n"); + } + + yield $counter++ => $line; + + } while (true); + })(static::open($file, 'r')); + } + + + /** + * Writes the string to a file. + * @throws Nette\IOException on error occurred + */ + public static function write(string $file, string $content, ?int $mode = 0666): void + { + static::createDir(dirname($file)); + if (@file_put_contents($file, $content) === false) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to write file '%s'. %s", + self::normalizePath($file), + Helpers::getLastError(), + )); + } + + if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod file '%s' to mode %s. %s", + self::normalizePath($file), + decoct($mode), + Helpers::getLastError(), + )); + } + } + + + /** + * Sets file permissions to `$fileMode` or directory permissions to `$dirMode`. + * Recursively traverses and sets permissions on the entire contents of the directory as well. + * @throws Nette\IOException on error occurred + */ + public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void + { + if (is_file($path)) { + if (!@chmod($path, $fileMode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod file '%s' to mode %s. %s", + self::normalizePath($path), + decoct($fileMode), + Helpers::getLastError(), + )); + } + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::makeWritable($item->getPathname(), $dirMode, $fileMode); + } + + if (!@chmod($path, $dirMode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod directory '%s' to mode %s. %s", + self::normalizePath($path), + decoct($dirMode), + Helpers::getLastError(), + )); + } + } else { + throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path))); + } + } + + + /** + * Determines if the path is absolute. + */ + public static function isAbsolute(string $path): bool + { + return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path); + } + + + /** + * Normalizes `..` and `.` and directory separators in path. + */ + public static function normalizePath(string $path): string + { + $parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path); + $res = []; + foreach ($parts as $part) { + if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') { + array_pop($res); + } elseif ($part !== '.') { + $res[] = $part; + } + } + + return $res === [''] + ? DIRECTORY_SEPARATOR + : implode(DIRECTORY_SEPARATOR, $res); + } + + + /** + * Joins all segments of the path and normalizes the result. + */ + public static function joinPaths(string ...$paths): string + { + return self::normalizePath(implode('/', $paths)); + } + + + /** + * Converts backslashes to slashes. + */ + public static function unixSlashes(string $path): string + { + return strtr($path, '\\', '/'); + } + + + /** + * Converts slashes to platform-specific directory separators. + */ + public static function platformSlashes(string $path): string + { + return DIRECTORY_SEPARATOR === '/' + ? strtr($path, '\\', '/') + : str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol:// + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php new file mode 100644 index 0000000..a496528 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Finder.php @@ -0,0 +1,510 @@ +size('> 10kB') + * ->from('.') + * ->exclude('temp'); + * + * @implements \IteratorAggregate + */ +class Finder implements \IteratorAggregate +{ + use Nette\SmartObject; + + /** @var array */ + private array $find = []; + + /** @var string[] */ + private array $in = []; + + /** @var \Closure[] */ + private array $filters = []; + + /** @var \Closure[] */ + private array $descentFilters = []; + + /** @var array */ + private array $appends = []; + private bool $childFirst = false; + + /** @var ?callable */ + private $sort; + private int $maxDepth = -1; + private bool $ignoreUnreadableDirs = true; + + + /** + * Begins search for files and directories matching mask. + */ + public static function find(string|array $masks = ['*']): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'dir')->addMask($masks, 'file'); + } + + + /** + * Begins search for files matching mask. + */ + public static function findFiles(string|array $masks = ['*']): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'file'); + } + + + /** + * Begins search for directories matching mask. + */ + public static function findDirectories(string|array $masks = ['*']): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'dir'); + } + + + /** + * Finds files matching the specified masks. + */ + public function files(string|array $masks = ['*']): static + { + return $this->addMask((array) $masks, 'file'); + } + + + /** + * Finds directories matching the specified masks. + */ + public function directories(string|array $masks = ['*']): static + { + return $this->addMask((array) $masks, 'dir'); + } + + + private function addMask(array $masks, string $mode): static + { + foreach ($masks as $mask) { + $mask = FileSystem::unixSlashes($mask); + if ($mode === 'dir') { + $mask = rtrim($mask, '/'); + } + if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) { + throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); + } + if (str_starts_with($mask, '**/')) { + $mask = substr($mask, 3); + } + $this->find[] = [$mask, $mode]; + } + return $this; + } + + + /** + * Searches in the given directories. Wildcards are allowed. + */ + public function in(string|array $paths): static + { + $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic + $this->addLocation($paths, ''); + return $this; + } + + + /** + * Searches recursively from the given directories. Wildcards are allowed. + */ + public function from(string|array $paths): static + { + $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic + $this->addLocation($paths, '/**'); + return $this; + } + + + private function addLocation(array $paths, string $ext): void + { + foreach ($paths as $path) { + if ($path === '') { + throw new Nette\InvalidArgumentException("Invalid directory '$path'"); + } + $path = rtrim(FileSystem::unixSlashes($path), '/'); + $this->in[] = $path . $ext; + } + } + + + /** + * Lists directory's contents before the directory itself. By default, this is disabled. + */ + public function childFirst(bool $state = true): static + { + $this->childFirst = $state; + return $this; + } + + + /** + * Ignores unreadable directories. By default, this is enabled. + */ + public function ignoreUnreadableDirs(bool $state = true): static + { + $this->ignoreUnreadableDirs = $state; + return $this; + } + + + /** + * Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory. + * @param callable(FileInfo, FileInfo): int $callback + */ + public function sortBy(callable $callback): static + { + $this->sort = $callback; + return $this; + } + + + /** + * Sorts files in each directory naturally by name. + */ + public function sortByName(): static + { + $this->sort = fn(FileInfo $a, FileInfo $b): int => strnatcmp($a->getBasename(), $b->getBasename()); + return $this; + } + + + /** + * Adds the specified paths or appends a new finder that returns. + */ + public function append(string|array|null $paths = null): static + { + if ($paths === null) { + return $this->appends[] = new static; + } + + $this->appends = array_merge($this->appends, (array) $paths); + return $this; + } + + + /********************* filtering ****************d*g**/ + + + /** + * Skips entries that matches the given masks relative to the ones defined with the in() or from() methods. + */ + public function exclude(string|array $masks): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + foreach ($masks as $mask) { + $mask = FileSystem::unixSlashes($mask); + if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) { + throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); + } + $end = $m[3]; + $re = $this->buildPattern($m[2]); + $filter = fn(FileInfo $file): bool => ($end && !$file->isDir()) + || !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname())); + + $this->descentFilter($filter); + if ($end !== '/*') { + $this->filter($filter); + } + } + + return $this; + } + + + /** + * Yields only entries which satisfy the given filter. + * @param callable(FileInfo): bool $callback + */ + public function filter(callable $callback): static + { + $this->filters[] = \Closure::fromCallable($callback); + return $this; + } + + + /** + * It descends only to directories that match the specified filter. + * @param callable(FileInfo): bool $callback + */ + public function descentFilter(callable $callback): static + { + $this->descentFilters[] = \Closure::fromCallable($callback); + return $this; + } + + + /** + * Sets the maximum depth of entries. + */ + public function limitDepth(?int $depth): static + { + $this->maxDepth = $depth ?? -1; + return $this; + } + + + /** + * Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB + */ + public function size(string $operator, ?int $size = null): static + { + if (func_num_args() === 1) { // in $operator is predicate + if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) { + throw new Nette\InvalidArgumentException('Invalid size predicate format.'); + } + + [, $operator, $size, $unit] = $matches; + $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9]; + $size *= $units[strtolower($unit)]; + $operator = $operator ?: '='; + } + + return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size)); + } + + + /** + * Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23 + */ + public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static + { + if (func_num_args() === 1) { // in $operator is predicate + if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) { + throw new Nette\InvalidArgumentException('Invalid date predicate format.'); + } + + [, $operator, $date] = $matches; + $operator = $operator ?: '='; + } + + $date = DateTime::from($date)->format('U'); + return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date)); + } + + + /********************* iterator generator ****************d*g**/ + + + /** + * Returns an array with all found files and directories. + * @return list + */ + public function collect(): array + { + return iterator_to_array($this->getIterator(), preserve_keys: false); + } + + + /** @return \Generator */ + public function getIterator(): \Generator + { + $plan = $this->buildPlan(); + foreach ($plan as $dir => $searches) { + yield from $this->traverseDir($dir, $searches); + } + + foreach ($this->appends as $item) { + if ($item instanceof self) { + yield from $item->getIterator(); + } else { + $item = FileSystem::platformSlashes($item); + yield $item => new FileInfo($item); + } + } + } + + + /** + * @param array $searches + * @param string[] $subdirs + * @return \Generator + */ + private function traverseDir(string $dir, array $searches, array $subdirs = []): \Generator + { + if ($this->maxDepth >= 0 && count($subdirs) > $this->maxDepth) { + return; + } elseif (!is_dir($dir)) { + throw new Nette\InvalidStateException(sprintf("Directory '%s' does not exist.", rtrim($dir, '/\\'))); + } + + try { + $pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS); + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + return; + } else { + throw new Nette\InvalidStateException($e->getMessage()); + } + } + + $files = $this->convertToFiles($pathNames, implode('/', $subdirs), FileSystem::isAbsolute($dir)); + + if ($this->sort) { + $files = iterator_to_array($files); + usort($files, $this->sort); + } + + foreach ($files as $file) { + $pathName = $file->getPathname(); + $cache = $subSearch = []; + + if ($file->isDir()) { + foreach ($searches as $search) { + if ($search->recursive && $this->proveFilters($this->descentFilters, $file, $cache)) { + $subSearch[] = $search; + } + } + } + + if ($this->childFirst && $subSearch) { + yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); + } + + $relativePathname = FileSystem::unixSlashes($file->getRelativePathname()); + foreach ($searches as $search) { + if ( + $file->{'is' . $search->mode}() + && preg_match($search->pattern, $relativePathname) + && $this->proveFilters($this->filters, $file, $cache) + ) { + yield $pathName => $file; + break; + } + } + + if (!$this->childFirst && $subSearch) { + yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); + } + } + } + + + private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator + { + foreach ($pathNames as $pathName) { + if (!$absolute) { + $pathName = preg_replace('~\.?/~A', '', $pathName); + } + $pathName = FileSystem::platformSlashes($pathName); + yield new FileInfo($pathName, $relativePath); + } + } + + + private function proveFilters(array $filters, FileInfo $file, array &$cache): bool + { + foreach ($filters as $filter) { + $res = &$cache[spl_object_id($filter)]; + $res ??= $filter($file); + if (!$res) { + return false; + } + } + + return true; + } + + + /** @return array> */ + private function buildPlan(): array + { + $plan = $dirCache = []; + foreach ($this->find as [$mask, $mode]) { + $splits = []; + if (FileSystem::isAbsolute($mask)) { + if ($this->in) { + throw new Nette\InvalidStateException("You cannot combine the absolute path in the mask '$mask' and the directory to search '{$this->in[0]}'."); + } + $splits[] = self::splitRecursivePart($mask); + } else { + foreach ($this->in ?: ['.'] as $in) { + $in = strtr($in, ['[' => '[[]', ']' => '[]]']); // in path, do not treat [ and ] as a pattern by glob() + $splits[] = self::splitRecursivePart($in . '/' . $mask); + } + } + + foreach ($splits as [$base, $rest, $recursive]) { + $base = $base === '' ? '.' : $base; + $dirs = $dirCache[$base] ??= strpbrk($base, '*?[') + ? glob($base, GLOB_NOSORT | GLOB_ONLYDIR | GLOB_NOESCAPE) + : [strtr($base, ['[[]' => '[', '[]]' => ']'])]; // unescape [ and ] + + if (!$dirs) { + throw new Nette\InvalidStateException(sprintf("Directory '%s' does not exist.", rtrim($base, '/\\'))); + } + + $search = (object) ['pattern' => $this->buildPattern($rest), 'mode' => $mode, 'recursive' => $recursive]; + foreach ($dirs as $dir) { + $plan[$dir][] = $search; + } + } + } + + return $plan; + } + + + /** + * Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal. + */ + private static function splitRecursivePart(string $path): array + { + $a = strrpos($path, '/'); + $parts = preg_split('~(?<=^|/)\*\*($|/)~', substr($path, 0, $a + 1), 2); + return isset($parts[1]) + ? [$parts[0], $parts[1] . substr($path, $a + 1), true] + : [$parts[0], substr($path, $a + 1), false]; + } + + + /** + * Converts wildcards to regular expression. + */ + private function buildPattern(string $mask): string + { + if ($mask === '*') { + return '##'; + } elseif (str_starts_with($mask, './')) { + $anchor = '^'; + $mask = substr($mask, 2); + } else { + $anchor = '(?:^|/)'; + } + + $pattern = strtr( + preg_quote($mask, '#'), + [ + '\*\*/' => '(.+/)?', + '\*' => '[^/]*', + '\?' => '[^/]', + '\[\!' => '[^', + '\[' => '[', + '\]' => ']', + '\-' => '-', + ], + ); + return '#' . $anchor . $pattern . '$#D' . (defined('PHP_WINDOWS_VERSION_BUILD') ? 'i' : ''); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php new file mode 100644 index 0000000..cc2781d --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Floats.php @@ -0,0 +1,107 @@ + $b it returns 1 + * @throws \LogicException if one of parameters is NAN + */ + public static function compare(float $a, float $b): int + { + if (is_nan($a) || is_nan($b)) { + throw new \LogicException('Trying to compare NAN'); + + } elseif (!is_finite($a) && !is_finite($b) && $a === $b) { + return 0; + } + + $diff = abs($a - $b); + if (($diff < self::Epsilon || ($diff / max(abs($a), abs($b)) < self::Epsilon))) { + return 0; + } + + return $a < $b ? -1 : 1; + } + + + /** + * Returns true if $a = $b + * @throws \LogicException if one of parameters is NAN + */ + public static function areEqual(float $a, float $b): bool + { + return self::compare($a, $b) === 0; + } + + + /** + * Returns true if $a < $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isLessThan(float $a, float $b): bool + { + return self::compare($a, $b) < 0; + } + + + /** + * Returns true if $a <= $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isLessThanOrEqualTo(float $a, float $b): bool + { + return self::compare($a, $b) <= 0; + } + + + /** + * Returns true if $a > $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isGreaterThan(float $a, float $b): bool + { + return self::compare($a, $b) > 0; + } + + + /** + * Returns true if $a >= $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isGreaterThanOrEqualTo(float $a, float $b): bool + { + return self::compare($a, $b) >= 0; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php new file mode 100644 index 0000000..b3586c1 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Helpers.php @@ -0,0 +1,104 @@ + $max) { + throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max)."); + } + + return min(max($value, $min), $max); + } + + + /** + * Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding). + * @param string[] $possibilities + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities) as $item) { + if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) { + $min = $len; + $best = $item; + } + } + + return $best; + } + + + /** + * Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <> + */ + public static function compare(mixed $left, string $operator, mixed $right): bool + { + return match ($operator) { + '>' => $left > $right, + '>=' => $left >= $right, + '<' => $left < $right, + '<=' => $left <= $right, + '=', '==' => $left == $right, + '===' => $left === $right, + '!=', '<>' => $left != $right, + '!==' => $left !== $right, + default => throw new Nette\InvalidArgumentException("Unknown operator '$operator'"), + }; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php new file mode 100644 index 0000000..fc0e3ef --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Html.php @@ -0,0 +1,839 @@ + element's attributes */ + public $attrs = []; + + /** void elements */ + public static $emptyElements = [ + 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, + 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, + 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1, + ]; + + /** @var array nodes */ + protected $children = []; + + /** element's name */ + private string $name = ''; + + private bool $isEmpty = false; + + + /** + * Constructs new HTML element. + * @param array|string $attrs element's attributes or plain text content + */ + public static function el(?string $name = null, array|string|null $attrs = null): static + { + $el = new static; + $parts = explode(' ', (string) $name, 2); + $el->setName($parts[0]); + + if (is_array($attrs)) { + $el->attrs = $attrs; + + } elseif ($attrs !== null) { + $el->setText($attrs); + } + + if (isset($parts[1])) { + foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) { + $el->attrs[$m[1]] = $m[3] ?? true; + } + } + + return $el; + } + + + /** + * Returns an object representing HTML text. + */ + public static function fromHtml(string $html): static + { + return (new static)->setHtml($html); + } + + + /** + * Returns an object representing plain text. + */ + public static function fromText(string $text): static + { + return (new static)->setText($text); + } + + + /** + * Converts to HTML. + */ + final public function toHtml(): string + { + return $this->render(); + } + + + /** + * Converts to plain text. + */ + final public function toText(): string + { + return $this->getText(); + } + + + /** + * Converts given HTML code to plain text. + */ + public static function htmlToText(string $html): string + { + return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + + /** + * Changes element's name. + */ + final public function setName(string $name, ?bool $isEmpty = null): static + { + $this->name = $name; + $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]); + return $this; + } + + + /** + * Returns element's name. + */ + final public function getName(): string + { + return $this->name; + } + + + /** + * Is element empty? + */ + final public function isEmpty(): bool + { + return $this->isEmpty; + } + + + /** + * Sets multiple attributes. + */ + public function addAttributes(array $attrs): static + { + $this->attrs = array_merge($this->attrs, $attrs); + return $this; + } + + + /** + * Appends value to element's attribute. + */ + public function appendAttribute(string $name, mixed $value, mixed $option = true): static + { + if (is_array($value)) { + $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; + $this->attrs[$name] = $value + $prev; + + } elseif ((string) $value === '') { + $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists + + } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array + $this->attrs[$name][$value] = $option; + + } else { + $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option]; + } + + return $this; + } + + + /** + * Sets element's attribute. + */ + public function setAttribute(string $name, mixed $value): static + { + $this->attrs[$name] = $value; + return $this; + } + + + /** + * Returns element's attribute. + */ + public function getAttribute(string $name): mixed + { + return $this->attrs[$name] ?? null; + } + + + /** + * Unsets element's attribute. + */ + public function removeAttribute(string $name): static + { + unset($this->attrs[$name]); + return $this; + } + + + /** + * Unsets element's attributes. + */ + public function removeAttributes(array $attributes): static + { + foreach ($attributes as $name) { + unset($this->attrs[$name]); + } + + return $this; + } + + + /** + * Overloaded setter for element's attribute. + */ + final public function __set(string $name, mixed $value): void + { + $this->attrs[$name] = $value; + } + + + /** + * Overloaded getter for element's attribute. + */ + final public function &__get(string $name): mixed + { + return $this->attrs[$name]; + } + + + /** + * Overloaded tester for element's attribute. + */ + final public function __isset(string $name): bool + { + return isset($this->attrs[$name]); + } + + + /** + * Overloaded unsetter for element's attribute. + */ + final public function __unset(string $name): void + { + unset($this->attrs[$name]); + } + + + /** + * Overloaded setter for element's attribute. + */ + final public function __call(string $m, array $args): mixed + { + $p = substr($m, 0, 3); + if ($p === 'get' || $p === 'set' || $p === 'add') { + $m = substr($m, 3); + $m[0] = $m[0] | "\x20"; + if ($p === 'get') { + return $this->attrs[$m] ?? null; + + } elseif ($p === 'add') { + $args[] = true; + } + } + + if (count($args) === 0) { // invalid + + } elseif (count($args) === 1) { // set + $this->attrs[$m] = $args[0]; + + } else { // add + $this->appendAttribute($m, $args[0], $args[1]); + } + + return $this; + } + + + /** + * Special setter for element's attribute. + */ + final public function href(string $path, array $query = []): static + { + if ($query) { + $query = http_build_query($query, '', '&'); + if ($query !== '') { + $path .= '?' . $query; + } + } + + $this->attrs['href'] = $path; + return $this; + } + + + /** + * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'. + */ + public function data(string $name, mixed $value = null): static + { + if (func_num_args() === 1) { + $this->attrs['data'] = $name; + } else { + $this->attrs["data-$name"] = is_bool($value) + ? json_encode($value) + : $value; + } + + return $this; + } + + + /** + * Sets element's HTML content. + */ + final public function setHtml(mixed $html): static + { + $this->children = [(string) $html]; + return $this; + } + + + /** + * Returns element's HTML content. + */ + final public function getHtml(): string + { + return implode('', $this->children); + } + + + /** + * Sets element's textual content. + */ + final public function setText(mixed $text): static + { + if (!$text instanceof HtmlStringable) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + + $this->children = [(string) $text]; + return $this; + } + + + /** + * Returns element's textual content. + */ + final public function getText(): string + { + return self::htmlToText($this->getHtml()); + } + + + /** + * Adds new element's child. + */ + final public function addHtml(mixed $child): static + { + return $this->insert(null, $child); + } + + + /** + * Appends plain-text string to element content. + */ + public function addText(mixed $text): static + { + if (!$text instanceof HtmlStringable) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + + return $this->insert(null, $text); + } + + + /** + * Creates and adds a new Html child. + */ + final public function create(string $name, array|string|null $attrs = null): static + { + $this->insert(null, $child = static::el($name, $attrs)); + return $child; + } + + + /** + * Inserts child node. + */ + public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static + { + $child = $child instanceof self ? $child : (string) $child; + if ($index === null) { // append + $this->children[] = $child; + + } else { // insert or replace + array_splice($this->children, $index, $replace ? 1 : 0, [$child]); + } + + return $this; + } + + + /** + * Inserts (replaces) child node (\ArrayAccess implementation). + * @param int|null $index position or null for appending + * @param Html|string $child Html node or raw HTML string + */ + final public function offsetSet($index, $child): void + { + $this->insert($index, $child, replace: true); + } + + + /** + * Returns child node (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetGet($index): HtmlStringable|string + { + return $this->children[$index]; + } + + + /** + * Exists child node? (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetExists($index): bool + { + return isset($this->children[$index]); + } + + + /** + * Removes child node (\ArrayAccess implementation). + * @param int $index + */ + public function offsetUnset($index): void + { + if (isset($this->children[$index])) { + array_splice($this->children, $index, 1); + } + } + + + /** + * Returns children count. + */ + final public function count(): int + { + return count($this->children); + } + + + /** + * Removes all children. + */ + public function removeChildren(): void + { + $this->children = []; + } + + + /** + * Iterates over elements. + * @return \ArrayIterator + */ + final public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->children); + } + + + /** + * Returns all children. + */ + final public function getChildren(): array + { + return $this->children; + } + + + /** + * Renders element's start tag, content and end tag. + */ + final public function render(?int $indent = null): string + { + $s = $this->startTag(); + + if (!$this->isEmpty) { + // add content + if ($indent !== null) { + $indent++; + } + + foreach ($this->children as $child) { + if ($child instanceof self) { + $s .= $child->render($indent); + } else { + $s .= $child; + } + } + + // add end tag + $s .= $this->endTag(); + } + + if ($indent !== null) { + return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2)); + } + + return $s; + } + + + final public function __toString(): string + { + return $this->render(); + } + + + /** + * Returns element's start tag. + */ + final public function startTag(): string + { + return $this->name + ? '<' . $this->name . $this->attributes() . '>' + : ''; + } + + + /** + * Returns element's end tag. + */ + final public function endTag(): string + { + return $this->name && !$this->isEmpty ? 'name . '>' : ''; + } + + + /** + * Returns element's attributes. + * @internal + */ + final public function attributes(): string + { + if (!is_array($this->attrs)) { + return ''; + } + + $s = ''; + $attrs = $this->attrs; + foreach ($attrs as $key => $value) { + if ($value === null || $value === false) { + continue; + + } elseif ($value === true) { + $s .= ' ' . $key; + + continue; + + } elseif (is_array($value)) { + if (strncmp($key, 'data-', 5) === 0) { + $value = Json::encode($value); + + } else { + $tmp = null; + foreach ($value as $k => $v) { + if ($v != null) { // intentionally ==, skip nulls & empty string + // composite 'style' vs. 'others' + $tmp[] = $v === true + ? $k + : (is_string($k) ? $k . ':' . $v : $v); + } + } + + if ($tmp === null) { + continue; + } + + $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp); + } + } elseif (is_float($value)) { + $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); + + } else { + $value = (string) $value; + } + + $q = str_contains($value, '"') ? "'" : '"'; + $s .= ' ' . $key . '=' . $q + . str_replace( + ['&', $q, '<'], + ['&', $q === '"' ? '"' : ''', '<'], + $value, + ) + . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '') + . $q; + } + + $s = str_replace('@', '@', $s); + return $s; + } + + + /** + * Clones all children too. + */ + public function __clone() + { + foreach ($this->children as $key => $value) { + if (is_object($value)) { + $this->children[$key] = clone $value; + } + } + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php new file mode 100644 index 0000000..d2947c7 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Image.php @@ -0,0 +1,831 @@ + + * $image = Image::fromFile('nette.jpg'); + * $image->resize(150, 100); + * $image->sharpen(); + * $image->send(); + * + * + * @method Image affine(array $affine, ?array $clip = null) + * @method void alphaBlending(bool $enable) + * @method void antialias(bool $enable) + * @method void arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color) + * @method int colorAllocate(int $red, int $green, int $blue) + * @method int colorAllocateAlpha(int $red, int $green, int $blue, int $alpha) + * @method int colorAt(int $x, int $y) + * @method int colorClosest(int $red, int $green, int $blue) + * @method int colorClosestAlpha(int $red, int $green, int $blue, int $alpha) + * @method int colorClosestHWB(int $red, int $green, int $blue) + * @method void colorDeallocate(int $color) + * @method int colorExact(int $red, int $green, int $blue) + * @method int colorExactAlpha(int $red, int $green, int $blue, int $alpha) + * @method void colorMatch(Image $image2) + * @method int colorResolve(int $red, int $green, int $blue) + * @method int colorResolveAlpha(int $red, int $green, int $blue, int $alpha) + * @method void colorSet(int $index, int $red, int $green, int $blue, int $alpha = 0) + * @method array colorsForIndex(int $color) + * @method int colorsTotal() + * @method int colorTransparent(?int $color = null) + * @method void convolution(array $matrix, float $div, float $offset) + * @method void copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH) + * @method void copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct) + * @method void copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct) + * @method void copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH) + * @method void copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH) + * @method Image cropAuto(int $mode = IMG_CROP_DEFAULT, float $threshold = .5, ?ImageColor $color = null) + * @method void ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color) + * @method void fill(int $x, int $y, ImageColor $color) + * @method void filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style) + * @method void filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color) + * @method void filledPolygon(array $points, ImageColor $color) + * @method void filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color) + * @method void fillToBorder(int $x, int $y, ImageColor $borderColor, ImageColor $color) + * @method void filter(int $filter, ...$args) + * @method void flip(int $mode) + * @method array ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options = []) + * @method void gammaCorrect(float $inputgamma, float $outputgamma) + * @method array getClip() + * @method int getInterpolation() + * @method int interlace(?bool $enable = null) + * @method bool isTrueColor() + * @method void layerEffect(int $effect) + * @method void line(int $x1, int $y1, int $x2, int $y2, ImageColor $color) + * @method void openPolygon(array $points, ImageColor $color) + * @method void paletteCopy(Image $source) + * @method void paletteToTrueColor() + * @method void polygon(array $points, ImageColor $color) + * @method void rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color) + * @method mixed resolution(?int $resolutionX = null, ?int $resolutionY = null) + * @method Image rotate(float $angle, ImageColor $backgroundColor) + * @method void saveAlpha(bool $enable) + * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) + * @method void setBrush(Image $brush) + * @method void setClip(int $x1, int $y1, int $x2, int $y2) + * @method void setInterpolation(int $method = IMG_BILINEAR_FIXED) + * @method void setPixel(int $x, int $y, ImageColor $color) + * @method void setStyle(array $style) + * @method void setThickness(int $thickness) + * @method void setTile(Image $tile) + * @method void trueColorToPalette(bool $dither, int $ncolors) + * @method array ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array $options = []) + * @property-read positive-int $width + * @property-read positive-int $height + * @property-read \GdImage $imageResource + */ +class Image +{ + use Nette\SmartObject; + + /** Prevent from getting resized to a bigger size than the original */ + public const ShrinkOnly = 0b0001; + + /** Resizes to a specified width and height without keeping aspect ratio */ + public const Stretch = 0b0010; + + /** Resizes to fit into a specified width and height and preserves aspect ratio */ + public const OrSmaller = 0b0000; + + /** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */ + public const OrBigger = 0b0100; + + /** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */ + public const Cover = 0b1000; + + /** @deprecated use Image::ShrinkOnly */ + public const SHRINK_ONLY = self::ShrinkOnly; + + /** @deprecated use Image::Stretch */ + public const STRETCH = self::Stretch; + + /** @deprecated use Image::OrSmaller */ + public const FIT = self::OrSmaller; + + /** @deprecated use Image::OrBigger */ + public const FILL = self::OrBigger; + + /** @deprecated use Image::Cover */ + public const EXACT = self::Cover; + + /** @deprecated use Image::EmptyGIF */ + public const EMPTY_GIF = self::EmptyGIF; + + /** image types */ + public const + JPEG = ImageType::JPEG, + PNG = ImageType::PNG, + GIF = ImageType::GIF, + WEBP = ImageType::WEBP, + AVIF = ImageType::AVIF, + BMP = ImageType::BMP; + + public const EmptyGIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"; + + private const Formats = [ImageType::JPEG => 'jpeg', ImageType::PNG => 'png', ImageType::GIF => 'gif', ImageType::WEBP => 'webp', ImageType::AVIF => 'avif', ImageType::BMP => 'bmp']; + + private \GdImage $image; + + + /** + * Returns RGB color (0..255) and transparency (0..127). + * @deprecated use ImageColor::rgb() + */ + public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array + { + return [ + 'red' => max(0, min(255, $red)), + 'green' => max(0, min(255, $green)), + 'blue' => max(0, min(255, $blue)), + 'alpha' => max(0, min(127, $transparency)), + ]; + } + + + /** + * Reads an image from a file and returns its type in $type. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws UnknownImageFileException if file not found or file type is not known + */ + public static function fromFile(string $file, ?int &$type = null): static + { + self::ensureExtension(); + $type = self::detectTypeFromFile($file); + if (!$type) { + throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found."); + } + + return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__); + } + + + /** + * Reads an image from a string and returns its type in $type. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws ImageException + */ + public static function fromString(string $s, ?int &$type = null): static + { + self::ensureExtension(); + $type = self::detectTypeFromString($s); + if (!$type) { + throw new UnknownImageFileException('Unknown type of image.'); + } + + return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__); + } + + + private static function invokeSafe(string $func, string $arg, string $message, string $callee): static + { + $errors = []; + $res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void { + $errors[] = $message; + }); + + if (!$res) { + throw new ImageException($message . ' Errors: ' . implode(', ', $errors)); + } elseif ($errors) { + trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING); + } + + return new static($res); + } + + + /** + * Creates a new true color image of the given dimensions. The default color is black. + * @param positive-int $width + * @param positive-int $height + * @throws Nette\NotSupportedException if gd extension is not loaded + */ + public static function fromBlank(int $width, int $height, ImageColor|array|null $color = null): static + { + self::ensureExtension(); + if ($width < 1 || $height < 1) { + throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.'); + } + + $image = new static(imagecreatetruecolor($width, $height)); + if ($color) { + $image->alphablending(false); + $image->filledrectangle(0, 0, $width - 1, $height - 1, $color); + $image->alphablending(true); + } + + return $image; + } + + + /** + * Returns the type of image from file. + * @return ImageType::*|null + */ + public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int + { + [$width, $height, $type] = @getimagesize($file); // @ - files smaller than 12 bytes causes read error + return isset(self::Formats[$type]) ? $type : null; + } + + + /** + * Returns the type of image from string. + * @return ImageType::*|null + */ + public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int + { + [$width, $height, $type] = @getimagesizefromstring($s); // @ - strings smaller than 12 bytes causes read error + return isset(self::Formats[$type]) ? $type : null; + } + + + /** + * Returns the file extension for the given image type. + * @param ImageType::* $type + * @return value-of + */ + public static function typeToExtension(int $type): string + { + if (!isset(self::Formats[$type])) { + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + + return self::Formats[$type]; + } + + + /** + * Returns the image type for given file extension. + * @return ImageType::* + */ + public static function extensionToType(string $extension): int + { + $extensions = array_flip(self::Formats) + ['jpg' => ImageType::JPEG]; + $extension = strtolower($extension); + if (!isset($extensions[$extension])) { + throw new Nette\InvalidArgumentException("Unsupported file extension '$extension'."); + } + + return $extensions[$extension]; + } + + + /** + * Returns the mime type for the given image type. + * @param ImageType::* $type + */ + public static function typeToMimeType(int $type): string + { + return 'image/' . self::typeToExtension($type); + } + + + /** + * @param ImageType::* $type + */ + public static function isTypeSupported(int $type): bool + { + self::ensureExtension(); + return (bool) (imagetypes() & match ($type) { + ImageType::JPEG => IMG_JPG, + ImageType::PNG => IMG_PNG, + ImageType::GIF => IMG_GIF, + ImageType::WEBP => IMG_WEBP, + ImageType::AVIF => 256, // IMG_AVIF, + ImageType::BMP => IMG_BMP, + default => 0, + }); + } + + + /** @return ImageType[] */ + public static function getSupportedTypes(): array + { + self::ensureExtension(); + $flag = imagetypes(); + return array_filter([ + $flag & IMG_GIF ? ImageType::GIF : null, + $flag & IMG_JPG ? ImageType::JPEG : null, + $flag & IMG_PNG ? ImageType::PNG : null, + $flag & IMG_WEBP ? ImageType::WEBP : null, + $flag & 256 ? ImageType::AVIF : null, // IMG_AVIF + $flag & IMG_BMP ? ImageType::BMP : null, + ]); + } + + + /** + * Wraps GD image. + */ + public function __construct(\GdImage $image) + { + $this->setImageResource($image); + imagesavealpha($image, true); + } + + + /** + * Returns image width. + * @return positive-int + */ + public function getWidth(): int + { + return imagesx($this->image); + } + + + /** + * Returns image height. + * @return positive-int + */ + public function getHeight(): int + { + return imagesy($this->image); + } + + + /** + * Sets image resource. + */ + protected function setImageResource(\GdImage $image): static + { + $this->image = $image; + return $this; + } + + + /** + * Returns image GD resource. + */ + public function getImageResource(): \GdImage + { + return $this->image; + } + + + /** + * Scales an image. Width and height accept pixels or percent. + * @param int-mask-of $mode + */ + public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static + { + if ($mode & self::Cover) { + return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height); + } + + [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $mode); + + if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize + $newImage = static::fromBlank($newWidth, $newHeight, ImageColor::rgb(0, 0, 0, 0))->getImageResource(); + imagecopyresampled( + $newImage, + $this->image, + 0, + 0, + 0, + 0, + $newWidth, + $newHeight, + $this->getWidth(), + $this->getHeight(), + ); + $this->image = $newImage; + } + + if ($width < 0 || $height < 0) { + imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL); + } + + return $this; + } + + + /** + * Calculates dimensions of resized image. Width and height accept pixels or percent. + * @param int-mask-of $mode + */ + public static function calculateSize( + int $srcWidth, + int $srcHeight, + $newWidth, + $newHeight, + int $mode = self::OrSmaller, + ): array + { + if ($newWidth === null) { + } elseif (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * abs($newWidth)); + $percents = true; + } else { + $newWidth = abs($newWidth); + } + + if ($newHeight === null) { + } elseif (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * abs($newHeight)); + $mode |= empty($percents) ? 0 : self::Stretch; + } else { + $newHeight = abs($newHeight); + } + + if ($mode & self::Stretch) { // non-proportional + if (!$newWidth || !$newHeight) { + throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.'); + } + + if ($mode & self::ShrinkOnly) { + $newWidth = min($srcWidth, $newWidth); + $newHeight = min($srcHeight, $newHeight); + } + } else { // proportional + if (!$newWidth && !$newHeight) { + throw new Nette\InvalidArgumentException('At least width or height must be specified.'); + } + + $scale = []; + if ($newWidth > 0) { // fit width + $scale[] = $newWidth / $srcWidth; + } + + if ($newHeight > 0) { // fit height + $scale[] = $newHeight / $srcHeight; + } + + if ($mode & self::OrBigger) { + $scale = [max($scale)]; + } + + if ($mode & self::ShrinkOnly) { + $scale[] = 1; + } + + $scale = min($scale); + $newWidth = (int) round($srcWidth * $scale); + $newHeight = (int) round($srcHeight * $scale); + } + + return [max($newWidth, 1), max($newHeight, 1)]; + } + + + /** + * Crops image. Arguments accepts pixels or percent. + */ + public function crop(int|string $left, int|string $top, int|string $width, int|string $height): static + { + [$r['x'], $r['y'], $r['width'], $r['height']] + = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); + if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') { + $this->image = imagecrop($this->image, $r); + imagesavealpha($this->image, true); + } else { + $newImage = static::fromBlank($r['width'], $r['height'], ImageColor::rgb(0, 0, 0, 0))->getImageResource(); + imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']); + $this->image = $newImage; + } + + return $this; + } + + + /** + * Calculates dimensions of cutout in image. Arguments accepts pixels or percent. + */ + public static function calculateCutout( + int $srcWidth, + int $srcHeight, + int|string $left, + int|string $top, + int|string $newWidth, + int|string $newHeight, + ): array + { + if (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * $newWidth); + } + + if (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * $newHeight); + } + + if (self::isPercent($left)) { + $left = (int) round(($srcWidth - $newWidth) / 100 * $left); + } + + if (self::isPercent($top)) { + $top = (int) round(($srcHeight - $newHeight) / 100 * $top); + } + + if ($left < 0) { + $newWidth += $left; + $left = 0; + } + + if ($top < 0) { + $newHeight += $top; + $top = 0; + } + + $newWidth = min($newWidth, $srcWidth - $left); + $newHeight = min($newHeight, $srcHeight - $top); + return [$left, $top, $newWidth, $newHeight]; + } + + + /** + * Sharpens image a little bit. + */ + public function sharpen(): static + { + imageconvolution($this->image, [ // my magic numbers ;) + [-1, -1, -1], + [-1, 24, -1], + [-1, -1, -1], + ], 16, 0); + return $this; + } + + + /** + * Puts another image into this image. Left and top accepts pixels or percent. + * @param int<0, 100> $opacity 0..100 + */ + public function place(self $image, int|string $left = 0, int|string $top = 0, int $opacity = 100): static + { + $opacity = max(0, min(100, $opacity)); + if ($opacity === 0) { + return $this; + } + + $width = $image->getWidth(); + $height = $image->getHeight(); + + if (self::isPercent($left)) { + $left = (int) round(($this->getWidth() - $width) / 100 * $left); + } + + if (self::isPercent($top)) { + $top = (int) round(($this->getHeight() - $height) / 100 * $top); + } + + $output = $input = $image->image; + if ($opacity < 100) { + $tbl = []; + for ($i = 0; $i < 128; $i++) { + $tbl[$i] = round(127 - (127 - $i) * $opacity / 100); + } + + $output = imagecreatetruecolor($width, $height); + imagealphablending($output, false); + if (!$image->isTrueColor()) { + $input = $output; + imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127)); + imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height); + } + + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $c = \imagecolorat($input, $x, $y); + $c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24); + \imagesetpixel($output, $x, $y, $c); + } + } + + imagealphablending($output, true); + } + + imagecopy( + $this->image, + $output, + $left, + $top, + 0, + 0, + $width, + $height, + ); + return $this; + } + + + /** + * Calculates the bounding box for a TrueType text. Returns keys left, top, width and height. + */ + public static function calculateTextBox( + string $text, + string $fontFile, + float $size, + float $angle = 0, + array $options = [], + ): array + { + self::ensureExtension(); + $box = imagettfbbox($size, $angle, $fontFile, $text, $options); + return [ + 'left' => $minX = min([$box[0], $box[2], $box[4], $box[6]]), + 'top' => $minY = min([$box[1], $box[3], $box[5], $box[7]]), + 'width' => max([$box[0], $box[2], $box[4], $box[6]]) - $minX + 1, + 'height' => max([$box[1], $box[3], $box[5], $box[7]]) - $minY + 1, + ]; + } + + + /** + * Draw a rectangle. + */ + public function rectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void + { + if ($width !== 0 && $height !== 0) { + $this->rectangle($x, $y, $x + $width + ($width > 0 ? -1 : 1), $y + $height + ($height > 0 ? -1 : 1), $color); + } + } + + + /** + * Draw a filled rectangle. + */ + public function filledRectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void + { + if ($width !== 0 && $height !== 0) { + $this->filledRectangle($x, $y, $x + $width + ($width > 0 ? -1 : 1), $y + $height + ($height > 0 ? -1 : 1), $color); + } + } + + + /** + * Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @param ImageType::*|null $type + * @throws ImageException + */ + public function save(string $file, ?int $quality = null, ?int $type = null): void + { + $type ??= self::extensionToType(pathinfo($file, PATHINFO_EXTENSION)); + $this->output($type, $quality, $file); + } + + + /** + * Outputs image to string. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @param ImageType::* $type + */ + public function toString(int $type = ImageType::JPEG, ?int $quality = null): string + { + return Helpers::capture(function () use ($type, $quality): void { + $this->output($type, $quality); + }); + } + + + /** + * Outputs image to string. + */ + public function __toString(): string + { + return $this->toString(); + } + + + /** + * Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @param ImageType::* $type + * @throws ImageException + */ + public function send(int $type = ImageType::JPEG, ?int $quality = null): void + { + header('Content-Type: ' . self::typeToMimeType($type)); + $this->output($type, $quality); + } + + + /** + * Outputs image to browser or file. + * @param ImageType::* $type + * @throws ImageException + */ + private function output(int $type, ?int $quality, ?string $file = null): void + { + switch ($type) { + case ImageType::JPEG: + $quality = $quality === null ? 85 : max(0, min(100, $quality)); + $success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception + break; + + case ImageType::PNG: + $quality = $quality === null ? 9 : max(0, min(9, $quality)); + $success = @imagepng($this->image, $file, $quality); // @ is escalated to exception + break; + + case ImageType::GIF: + $success = @imagegif($this->image, $file); // @ is escalated to exception + break; + + case ImageType::WEBP: + $quality = $quality === null ? 80 : max(0, min(100, $quality)); + $success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception + break; + + case ImageType::AVIF: + $quality = $quality === null ? 30 : max(0, min(100, $quality)); + $success = @imageavif($this->image, $file, $quality); // @ is escalated to exception + break; + + case ImageType::BMP: + $success = @imagebmp($this->image, $file); // @ is escalated to exception + break; + + default: + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + + if (!$success) { + throw new ImageException(Helpers::getLastError() ?: 'Unknown error'); + } + } + + + /** + * Call to undefined method. + * @throws Nette\MemberAccessException + */ + public function __call(string $name, array $args): mixed + { + $function = 'image' . $name; + if (!function_exists($function)) { + ObjectHelpers::strictCall(static::class, $name); + } + + foreach ($args as $key => $value) { + if ($value instanceof self) { + $args[$key] = $value->getImageResource(); + + } elseif ($value instanceof ImageColor || (is_array($value) && isset($value['red']))) { + $args[$key] = $this->resolveColor($value); + } + } + + $res = $function($this->image, ...$args); + return $res instanceof \GdImage + ? $this->setImageResource($res) + : $res; + } + + + public function __clone() + { + ob_start(function () {}); + imagepng($this->image, null, 0); + $this->setImageResource(imagecreatefromstring(ob_get_clean())); + } + + + private static function isPercent(int|string &$num): bool + { + if (is_string($num) && str_ends_with($num, '%')) { + $num = (float) substr($num, 0, -1); + return true; + } elseif (is_int($num) || $num === (string) (int) $num) { + $num = (int) $num; + return false; + } + + throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given."); + } + + + /** + * Prevents serialization. + */ + public function __sleep(): array + { + throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.'); + } + + + public function resolveColor(ImageColor|array $color): int + { + $color = $color instanceof ImageColor ? $color->toRGBA() : array_values($color); + return imagecolorallocatealpha($this->image, ...$color) ?: imagecolorresolvealpha($this->image, ...$color); + } + + + private static function ensureExtension(): void + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php new file mode 100644 index 0000000..013adbd --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageColor.php @@ -0,0 +1,75 @@ +red = max(0, min(255, $red)); + $this->green = max(0, min(255, $green)); + $this->blue = max(0, min(255, $blue)); + $this->opacity = max(0, min(1, $opacity)); + } + + + public function toRGBA(): array + { + return [ + max(0, min(255, $this->red)), + max(0, min(255, $this->green)), + max(0, min(255, $this->blue)), + max(0, min(127, (int) round(127 - $this->opacity * 127))), + ]; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php new file mode 100644 index 0000000..3092c8f --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ImageType.php @@ -0,0 +1,25 @@ + $v) { + if ($k === $key) { + return true; + } + } + return false; + } + + + /** + * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K + * @template V + * @param iterable $iterable + * @param ?callable(V, K, iterable): bool $predicate + * @return ?V + */ + public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed + { + foreach ($iterable as $k => $v) { + if (!$predicate || $predicate($v, $k, $iterable)) { + return $v; + } + } + return $else ? $else() : null; + } + + + /** + * Returns the key of first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null. + * @template K + * @template V + * @param iterable $iterable + * @param ?callable(V, K, iterable): bool $predicate + * @return ?K + */ + public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed + { + foreach ($iterable as $k => $v) { + if (!$predicate || $predicate($v, $k, $iterable)) { + return $k; + } + } + return $else ? $else() : null; + } + + + /** + * Tests whether at least one element in the iterator passes the test implemented by the provided function. + * @template K + * @template V + * @param iterable $iterable + * @param callable(V, K, iterable): bool $predicate + */ + public static function some(iterable $iterable, callable $predicate): bool + { + foreach ($iterable as $k => $v) { + if ($predicate($v, $k, $iterable)) { + return true; + } + } + return false; + } + + + /** + * Tests whether all elements in the iterator pass the test implemented by the provided function. + * @template K + * @template V + * @param iterable $iterable + * @param callable(V, K, iterable): bool $predicate + */ + public static function every(iterable $iterable, callable $predicate): bool + { + foreach ($iterable as $k => $v) { + if (!$predicate($v, $k, $iterable)) { + return false; + } + } + return true; + } + + + /** + * Iterator that filters elements according to a given $predicate. Maintains original keys. + * @template K + * @template V + * @param iterable $iterable + * @param callable(V, K, iterable): bool $predicate + * @return \Generator + */ + public static function filter(iterable $iterable, callable $predicate): \Generator + { + foreach ($iterable as $k => $v) { + if ($predicate($v, $k, $iterable)) { + yield $k => $v; + } + } + } + + + /** + * Iterator that transforms values by calling $transformer. Maintains original keys. + * @template K + * @template V + * @template R + * @param iterable $iterable + * @param callable(V, K, iterable): R $transformer + * @return \Generator + */ + public static function map(iterable $iterable, callable $transformer): \Generator + { + foreach ($iterable as $k => $v) { + yield $k => $transformer($v, $k, $iterable); + } + } + + + /** + * Iterator that transforms keys and values by calling $transformer. If it returns null, the element is skipped. + * @template K + * @template V + * @template ResV + * @template ResK + * @param iterable $iterable + * @param callable(V, K, iterable): ?array{ResV, ResK} $transformer + * @return \Generator + */ + public static function mapWithKeys(iterable $iterable, callable $transformer): \Generator + { + foreach ($iterable as $k => $v) { + $pair = $transformer($v, $k, $iterable); + if ($pair) { + yield $pair[0] => $pair[1]; + } + } + } + + + /** + * Wraps around iterator and caches its keys and values during iteration. + * This allows the data to be re-iterated multiple times. + * @template K + * @template V + * @param iterable $iterable + * @return \IteratorAggregate + */ + public static function memoize(iterable $iterable): iterable + { + return new class (self::toIterator($iterable)) implements \IteratorAggregate { + public function __construct( + private \Iterator $iterator, + private array $cache = [], + ) { + } + + + public function getIterator(): \Generator + { + if (!$this->cache) { + $this->iterator->rewind(); + } + $i = 0; + while (true) { + if (isset($this->cache[$i])) { + [$k, $v] = $this->cache[$i]; + } elseif ($this->iterator->valid()) { + $k = $this->iterator->key(); + $v = $this->iterator->current(); + $this->iterator->next(); + $this->cache[$i] = [$k, $v]; + } else { + break; + } + yield $k => $v; + $i++; + } + } + }; + } + + + /** + * Creates an iterator from anything that is iterable. + * @template K + * @template V + * @param iterable $iterable + * @return \Iterator + */ + public static function toIterator(iterable $iterable): \Iterator + { + return match (true) { + $iterable instanceof \Iterator => $iterable, + $iterable instanceof \IteratorAggregate => self::toIterator($iterable->getIterator()), + is_array($iterable) => new \ArrayIterator($iterable), + }; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php new file mode 100644 index 0000000..b87917b --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Json.php @@ -0,0 +1,84 @@ +getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + ), $name); + throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictSet(string $class, string $name): void + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + ), $name); + throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictCall(string $class, string $method, array $additionalMethods = []): void + { + $trace = debug_backtrace(0, 3); // suppose this method is called from __call() + $context = ($trace[1]['function'] ?? null) === '__call' + ? ($trace[2]['class'] ?? null) + : null; + + if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() + $class = get_parent_class($context); + } + + if (method_exists($class, $method)) { // insufficient visibility + $rm = new \ReflectionMethod($class, $method); + $visibility = $rm->isPrivate() + ? 'private ' + : ($rm->isProtected() ? 'protected ' : ''); + throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); + + } else { + $hint = self::getSuggestion(array_merge( + get_class_methods($class), + self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'), + $additionalMethods, + ), $method); + throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictStaticCall(string $class, string $method): void + { + $trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic() + $context = ($trace[1]['function'] ?? null) === '__callStatic' + ? ($trace[2]['class'] ?? null) + : null; + + if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() + $class = get_parent_class($context); + } + + if (method_exists($class, $method)) { // insufficient visibility + $rm = new \ReflectionMethod($class, $method); + $visibility = $rm->isPrivate() + ? 'private ' + : ($rm->isProtected() ? 'protected ' : ''); + throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); + + } else { + $hint = self::getSuggestion( + array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()), + $method, + ); + throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + } + + + /** + * Returns array of magic properties defined by annotation @property. + * @return array of [name => bit mask] + * @internal + */ + public static function getMagicProperties(string $class): array + { + static $cache; + $props = &$cache[$class]; + if ($props !== null) { + return $props; + } + + $rc = new \ReflectionClass($class); + preg_match_all( + '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + (string) $rc->getDocComment(), + $matches, + PREG_SET_ORDER, + ); + + $props = []; + foreach ($matches as [, $type, $name]) { + $uname = ucfirst($name); + $write = $type !== '-read' + && $rc->hasMethod($nm = 'set' . $uname) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + $read = $type !== '-write' + && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + + if ($read || $write) { + $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4; + } + } + + foreach ($rc->getTraits() as $trait) { + $props += self::getMagicProperties($trait->name); + } + + if ($parent = get_parent_class($class)) { + $props += self::getMagicProperties($parent); + } + + return $props; + } + + + /** + * Finds the best suggestion (for 8-bit encoding). + * @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities + * @internal + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value); + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities, SORT_REGULAR) as $item) { + $item = $item instanceof \Reflector ? $item->name : $item; + if ($item !== $value && ( + ($len = levenshtein($item, $value, 10, 11, 10)) < $min + || ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min + )) { + $min = $len; + $best = $item; + } + } + + return $best; + } + + + private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array + { + do { + $doc[] = $rc->getDocComment(); + $traits = $rc->getTraits(); + while ($trait = array_pop($traits)) { + $doc[] = $trait->getDocComment(); + $traits += $trait->getTraits(); + } + } while ($rc = $rc->getParentClass()); + + return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : []; + } + + + /** + * Checks if the public non-static property exists. + * Returns 'event' if the property exists and has event like name + * @internal + */ + public static function hasProperty(string $class, string $name): bool|string + { + static $cache; + $prop = &$cache[$class][$name]; + if ($prop === null) { + $prop = false; + try { + $rp = new \ReflectionProperty($class, $name); + if ($rp->isPublic() && !$rp->isStatic()) { + $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true; + } + } catch (\ReflectionException $e) { + } + } + + return $prop; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Paginator.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Paginator.php new file mode 100644 index 0000000..aa4812c --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Paginator.php @@ -0,0 +1,245 @@ + $firstItemOnPage + * @property-read int<0,max> $lastItemOnPage + * @property int $base + * @property-read bool $first + * @property-read bool $last + * @property-read int<0,max>|null $pageCount + * @property positive-int $itemsPerPage + * @property int<0,max>|null $itemCount + * @property-read int<0,max> $offset + * @property-read int<0,max>|null $countdownOffset + * @property-read int<0,max> $length + */ +class Paginator +{ + use Nette\SmartObject; + + private int $base = 1; + + /** @var positive-int */ + private int $itemsPerPage = 1; + + private int $page = 1; + + /** @var int<0, max>|null */ + private ?int $itemCount = null; + + + /** + * Sets current page number. + */ + public function setPage(int $page): static + { + $this->page = $page; + return $this; + } + + + /** + * Returns current page number. + */ + public function getPage(): int + { + return $this->base + $this->getPageIndex(); + } + + + /** + * Returns first page number. + */ + public function getFirstPage(): int + { + return $this->base; + } + + + /** + * Returns last page number. + */ + public function getLastPage(): ?int + { + return $this->itemCount === null + ? null + : $this->base + max(0, $this->getPageCount() - 1); + } + + + /** + * Returns the sequence number of the first element on the page + * @return int<0, max> + */ + public function getFirstItemOnPage(): int + { + return $this->itemCount !== 0 + ? $this->offset + 1 + : 0; + } + + + /** + * Returns the sequence number of the last element on the page + * @return int<0, max> + */ + public function getLastItemOnPage(): int + { + return $this->offset + $this->length; + } + + + /** + * Sets first page (base) number. + */ + public function setBase(int $base): static + { + $this->base = $base; + return $this; + } + + + /** + * Returns first page (base) number. + */ + public function getBase(): int + { + return $this->base; + } + + + /** + * Returns zero-based page number. + * @return int<0, max> + */ + protected function getPageIndex(): int + { + $index = max(0, $this->page - $this->base); + return $this->itemCount === null + ? $index + : min($index, max(0, $this->getPageCount() - 1)); + } + + + /** + * Is the current page the first one? + */ + public function isFirst(): bool + { + return $this->getPageIndex() === 0; + } + + + /** + * Is the current page the last one? + */ + public function isLast(): bool + { + return $this->itemCount === null + ? false + : $this->getPageIndex() >= $this->getPageCount() - 1; + } + + + /** + * Returns the total number of pages. + * @return int<0, max>|null + */ + public function getPageCount(): ?int + { + return $this->itemCount === null + ? null + : (int) ceil($this->itemCount / $this->itemsPerPage); + } + + + /** + * Sets the number of items to display on a single page. + */ + public function setItemsPerPage(int $itemsPerPage): static + { + $this->itemsPerPage = max(1, $itemsPerPage); + return $this; + } + + + /** + * Returns the number of items to display on a single page. + * @return positive-int + */ + public function getItemsPerPage(): int + { + return $this->itemsPerPage; + } + + + /** + * Sets the total number of items. + */ + public function setItemCount(?int $itemCount = null): static + { + $this->itemCount = $itemCount === null ? null : max(0, $itemCount); + return $this; + } + + + /** + * Returns the total number of items. + * @return int<0, max>|null + */ + public function getItemCount(): ?int + { + return $this->itemCount; + } + + + /** + * Returns the absolute index of the first item on current page. + * @return int<0, max> + */ + public function getOffset(): int + { + return $this->getPageIndex() * $this->itemsPerPage; + } + + + /** + * Returns the absolute index of the first item on current page in countdown paging. + * @return int<0, max>|null + */ + public function getCountdownOffset(): ?int + { + return $this->itemCount === null + ? null + : max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage); + } + + + /** + * Returns the number of items on current page. + * @return int<0, max> + */ + public function getLength(): int + { + return $this->itemCount === null + ? $this->itemsPerPage + : min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php new file mode 100644 index 0000000..b14fbd5 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Random.php @@ -0,0 +1,52 @@ + implode('', range($m[0][0], $m[0][2])), + $charlist, + ); + $charlist = count_chars($charlist, mode: 3); + $chLen = strlen($charlist); + + if ($length < 1) { + throw new Nette\InvalidArgumentException('Length must be greater than zero.'); + } elseif ($chLen < 2) { + throw new Nette\InvalidArgumentException('Character list must contain at least two chars.'); + } elseif (PHP_VERSION_ID >= 80300) { + return (new Randomizer)->getBytesFromString($charlist, $length); + } + + $res = ''; + for ($i = 0; $i < $length; $i++) { + $res .= $charlist[random_int(0, $chLen - 1)]; + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php new file mode 100644 index 0000000..87889be --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Reflection.php @@ -0,0 +1,322 @@ +isDefaultValueConstant()) { + $const = $orig = $param->getDefaultValueConstantName(); + $pair = explode('::', $const); + if (isset($pair[1])) { + $pair[0] = Type::resolve($pair[0], $param); + try { + $rcc = new \ReflectionClassConstant($pair[0], $pair[1]); + } catch (\ReflectionException $e) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e); + } + + return $rcc->getValue(); + + } elseif (!defined($const)) { + $const = substr((string) strrchr($const, '\\'), 1); + if (!defined($const)) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name."); + } + } + + return constant($const); + } + + return $param->getDefaultValue(); + } + + + /** + * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait. + */ + public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass + { + foreach ($prop->getDeclaringClass()->getTraits() as $trait) { + if ($trait->hasProperty($prop->name) + // doc-comment guessing as workaround for insufficient PHP reflection + && $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment() + ) { + return self::getPropertyDeclaringClass($trait->getProperty($prop->name)); + } + } + + return $prop->getDeclaringClass(); + } + + + /** + * Returns a reflection of a method that contains a declaration of $method. + * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name. + */ + public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod + { + // file & line guessing as workaround for insufficient PHP reflection + $decl = $method->getDeclaringClass(); + if ($decl->getFileName() === $method->getFileName() + && $decl->getStartLine() <= $method->getStartLine() + && $decl->getEndLine() >= $method->getEndLine() + ) { + return $method; + } + + $hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()]; + if (($alias = $decl->getTraitAliases()[$method->name] ?? null) + && ($m = new \ReflectionMethod(...explode('::', $alias, 2))) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + + foreach ($decl->getTraits() as $trait) { + if ($trait->hasMethod($method->name) + && ($m = $trait->getMethod($method->name)) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + } + + return $method; + } + + + /** + * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache. + */ + public static function areCommentsAvailable(): bool + { + static $res; + return $res ?? $res = (bool) (new \ReflectionMethod(self::class, __FUNCTION__))->getDocComment(); + } + + + public static function toString(\Reflector $ref): string + { + if ($ref instanceof \ReflectionClass) { + return $ref->name; + } elseif ($ref instanceof \ReflectionMethod) { + return $ref->getDeclaringClass()->name . '::' . $ref->name . '()'; + } elseif ($ref instanceof \ReflectionFunction) { + return PHP_VERSION_ID >= 80200 && $ref->isAnonymous() + ? '{closure}()' + : $ref->name . '()'; + } elseif ($ref instanceof \ReflectionProperty) { + return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name; + } elseif ($ref instanceof \ReflectionParameter) { + return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction()); + } else { + throw new Nette\InvalidArgumentException; + } + } + + + /** + * Expands the name of the class to full name in the given context of given class. + * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context. + * @throws Nette\InvalidArgumentException + */ + public static function expandClassName(string $name, \ReflectionClass $context): string + { + $lower = strtolower($name); + if (empty($name)) { + throw new Nette\InvalidArgumentException('Class name must not be empty.'); + + } elseif (Validators::isBuiltinType($lower)) { + return $lower; + + } elseif ($lower === 'self' || $lower === 'static') { + return $context->name; + + } elseif ($lower === 'parent') { + return $context->getParentClass() + ? $context->getParentClass()->name + : 'parent'; + + } elseif ($name[0] === '\\') { // fully qualified name + return ltrim($name, '\\'); + } + + $uses = self::getUseStatements($context); + $parts = explode('\\', $name, 2); + if (isset($uses[$parts[0]])) { + $parts[0] = $uses[$parts[0]]; + return implode('\\', $parts); + + } elseif ($context->inNamespace()) { + return $context->getNamespaceName() . '\\' . $name; + + } else { + return $name; + } + } + + + /** @return array of [alias => class] */ + public static function getUseStatements(\ReflectionClass $class): array + { + if ($class->isAnonymous()) { + throw new Nette\NotImplementedException('Anonymous classes are not supported.'); + } + + static $cache = []; + if (!isset($cache[$name = $class->name])) { + if ($class->isInternal()) { + $cache[$name] = []; + } else { + $code = file_get_contents($class->getFileName()); + $cache = self::parseUseStatements($code, $name) + $cache; + } + } + + return $cache[$name]; + } + + + /** + * Parses PHP code to [class => [alias => class, ...]] + */ + private static function parseUseStatements(string $code, ?string $forClass = null): array + { + try { + $tokens = \PhpToken::tokenize($code, TOKEN_PARSE); + } catch (\ParseError $e) { + trigger_error($e->getMessage(), E_USER_NOTICE); + $tokens = []; + } + + $namespace = $class = null; + $classLevel = $level = 0; + $res = $uses = []; + + $nameTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]; + + while ($token = current($tokens)) { + next($tokens); + switch ($token->id) { + case T_NAMESPACE: + $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\'); + $uses = []; + break; + + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + case PHP_VERSION_ID < 80100 + ? T_CLASS + : T_ENUM: + if ($name = self::fetch($tokens, T_STRING)) { + $class = $namespace . $name; + $classLevel = $level + 1; + $res[$class] = $uses; + if ($class === $forClass) { + return $res; + } + } + + break; + + case T_USE: + while (!$class && ($name = self::fetch($tokens, $nameTokens))) { + $name = ltrim($name, '\\'); + if (self::fetch($tokens, '{')) { + while ($suffix = self::fetch($tokens, $nameTokens)) { + if (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name . $suffix; + } else { + $tmp = explode('\\', $suffix); + $uses[end($tmp)] = $name . $suffix; + } + + if (!self::fetch($tokens, ',')) { + break; + } + } + } elseif (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name; + + } else { + $tmp = explode('\\', $name); + $uses[end($tmp)] = $name; + } + + if (!self::fetch($tokens, ',')) { + break; + } + } + + break; + + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case ord('{'): + $level++; + break; + + case ord('}'): + if ($level === $classLevel) { + $class = $classLevel = 0; + } + + $level--; + } + } + + return $res; + } + + + private static function fetch(array &$tokens, string|int|array $take): ?string + { + $res = null; + while ($token = current($tokens)) { + if ($token->is($take)) { + $res .= $token->text; + } elseif (!$token->is([T_DOC_COMMENT, T_WHITESPACE, T_COMMENT])) { + break; + } + + next($tokens); + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php b/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php new file mode 100644 index 0000000..b003fcb --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/ReflectionMethod.php @@ -0,0 +1,36 @@ +originalClass = new \ReflectionClass($objectOrMethod); + } + + + public function getOriginalClass(): \ReflectionClass + { + return $this->originalClass; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php new file mode 100644 index 0000000..c073565 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Strings.php @@ -0,0 +1,728 @@ += 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) { + throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.'); + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code)); + } + + + /** + * Returns a code point of specific character in UTF-8 (number in range 0x0000..D7FF or 0xE000..10FFFF). + */ + public static function ord(string $c): int + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + $tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c); + if (!$tmp) { + throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".'); + } + + return unpack('N', $tmp)[1]; + } + + + /** + * @deprecated use str_starts_with() + */ + public static function startsWith(string $haystack, string $needle): bool + { + return str_starts_with($haystack, $needle); + } + + + /** + * @deprecated use str_ends_with() + */ + public static function endsWith(string $haystack, string $needle): bool + { + return str_ends_with($haystack, $needle); + } + + + /** + * @deprecated use str_contains() + */ + public static function contains(string $haystack, string $needle): bool + { + return str_contains($haystack, $needle); + } + + + /** + * Returns a part of UTF-8 string specified by starting position and length. If start is negative, + * the returned string will start at the start'th character from the end of string. + */ + public static function substring(string $s, int $start, ?int $length = null): string + { + if (function_exists('mb_substr')) { + return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.'); + } elseif ($length === null) { + $length = self::length($s); + } elseif ($start < 0 && $length < 0) { + $start += self::length($s); // unifies iconv_substr behavior with mb_substr + } + + return iconv_substr($s, $start, $length, 'UTF-8'); + } + + + /** + * Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines, + * trims end spaces on lines, normalizes UTF-8 to the normal form of NFC. + */ + public static function normalize(string $s): string + { + // convert to compressed normal form (NFC) + if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) { + $s = $n; + } + + $s = self::unixNewLines($s); + + // remove control characters; leave \t + \n + $s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]); + + // right trim + $s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]); + + // leading and trailing blank lines + $s = trim($s, "\n"); + + return $s; + } + + + /** @deprecated use Strings::unixNewLines() */ + public static function normalizeNewLines(string $s): string + { + return self::unixNewLines($s); + } + + + /** + * Converts line endings to \n used on Unix-like systems. + * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. + */ + public static function unixNewLines(string $s): string + { + return preg_replace("~\r\n?|\u{2028}|\u{2029}~", "\n", $s); + } + + + /** + * Converts line endings to platform-specific, i.e. \r\n on Windows and \n elsewhere. + * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. + */ + public static function platformNewLines(string $s): string + { + return preg_replace("~\r\n?|\n|\u{2028}|\u{2029}~", PHP_EOL, $s); + } + + + /** + * Converts UTF-8 string to ASCII, ie removes diacritics etc. + */ + public static function toAscii(string $s): string + { + $iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null; + static $transliterator = null; + if ($transliterator === null) { + if (class_exists('Transliterator', false)) { + $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII'); + } else { + trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE); + $transliterator = false; + } + } + + // remove control characters and check UTF-8 validity + $s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]); + + // transliteration (by Transliterator and iconv) is not optimal, replace some characters directly + $s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß + if ($iconv !== 'libiconv') { + $s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔ + } + + if ($transliterator) { + $s = $transliterator->transliterate($s); + // use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ + if ($iconv === 'glibc') { + $s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + $s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters + } elseif ($iconv === 'libiconv') { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } else { // null or 'unknown' (#216) + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + } elseif ($iconv === 'glibc' || $iconv === 'libiconv') { + // temporarily hide these characters to distinguish them from the garbage that iconv creates + $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06"); + if ($iconv === 'glibc') { + // glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved + $s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); + $s = strtr( + $s, + "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7", + 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.', + ); + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); + } else { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } + + // remove garbage that iconv creates during transliteration (eg Ý -> Y') + $s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s); + // restore temporarily hidden characters + $s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?'); + } else { + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + + return $s; + } + + + /** + * Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters + * except letters of the English alphabet and numbers with a hyphens. + */ + public static function webalize(string $s, ?string $charlist = null, bool $lower = true): string + { + $s = self::toAscii($s); + if ($lower) { + $s = strtolower($s); + } + + $s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]); + $s = trim($s, '-'); + return $s; + } + + + /** + * Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated, + * an ellipsis (or something else set with third argument) is appended to the string. + */ + public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string + { + if (self::length($s) > $maxLen) { + $maxLen -= self::length($append); + if ($maxLen < 1) { + return $append; + + } elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) { + return $matches[0] . $append; + + } else { + return self::substring($s, 0, $maxLen) . $append; + } + } + + return $s; + } + + + /** + * Indents a multiline text from the left. Second argument sets how many indentation chars should be used, + * while the indent itself is the third argument (*tab* by default). + */ + public static function indent(string $s, int $level = 1, string $chars = "\t"): string + { + if ($level > 0) { + $s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level)); + } + + return $s; + } + + + /** + * Converts all characters of UTF-8 string to lower case. + */ + public static function lower(string $s): string + { + return mb_strtolower($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged. + */ + public static function firstLower(string $s): string + { + return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts all characters of a UTF-8 string to upper case. + */ + public static function upper(string $s): string + { + return mb_strtoupper($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged. + */ + public static function firstUpper(string $s): string + { + return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts the first character of every word of a UTF-8 string to upper case and the others to lower case. + */ + public static function capitalize(string $s): string + { + return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8'); + } + + + /** + * Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared, + * if it is negative, the corresponding number of characters from the end of the strings is compared, + * otherwise the appropriate number of characters from the beginning is compared. + */ + public static function compare(string $left, string $right, ?int $length = null): bool + { + if (class_exists('Normalizer', false)) { + $left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster + $right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster + } + + if ($length < 0) { + $left = self::substring($left, $length, -$length); + $right = self::substring($right, $length, -$length); + } elseif ($length !== null) { + $left = self::substring($left, 0, $length); + $right = self::substring($right, 0, $length); + } + + return self::lower($left) === self::lower($right); + } + + + /** + * Finds the common prefix of strings or returns empty string if the prefix was not found. + * @param string[] $strings + */ + public static function findPrefix(array $strings): string + { + $first = array_shift($strings); + for ($i = 0; $i < strlen($first); $i++) { + foreach ($strings as $s) { + if (!isset($s[$i]) || $first[$i] !== $s[$i]) { + while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") { + $i--; + } + + return substr($first, 0, $i); + } + } + } + + return $first; + } + + + /** + * Returns number of characters (not bytes) in UTF-8 string. + * That is the number of Unicode code points which may differ from the number of graphemes. + */ + public static function length(string $s): int + { + return match (true) { + extension_loaded('mbstring') => mb_strlen($s, 'UTF-8'), + extension_loaded('iconv') => iconv_strlen($s, 'UTF-8'), + default => strlen(@utf8_decode($s)), // deprecated + }; + } + + + /** + * Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string. + */ + public static function trim(string $s, string $charlist = self::TrimCharacters): string + { + $charlist = preg_quote($charlist, '#'); + return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', ''); + } + + + /** + * Pads a UTF-8 string to given length by prepending the $pad string to the beginning. + * @param non-empty-string $pad + */ + public static function padLeft(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s; + } + + + /** + * Pads UTF-8 string to given length by appending the $pad string to the end. + * @param non-empty-string $pad + */ + public static function padRight(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen); + } + + + /** + * Reverses UTF-8 string. + */ + public static function reverse(string $s): string + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s))); + } + + + /** + * Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function before(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, 0, $pos); + } + + + /** + * Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function after(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, $pos + strlen($needle)); + } + + + /** + * Returns position in characters of $nth occurence of $needle in $haystack or null if the $needle was not found. + * Negative value of `$nth` means searching from the end. + */ + public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : self::length(substr($haystack, 0, $pos)); + } + + + /** + * Returns position in characters of $nth occurence of $needle in $haystack or null if the needle was not found. + */ + private static function pos(string $haystack, string $needle, int $nth = 1): ?int + { + if (!$nth) { + return null; + } elseif ($nth > 0) { + if ($needle === '') { + return 0; + } + + $pos = 0; + while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) { + $pos++; + } + } else { + $len = strlen($haystack); + if ($needle === '') { + return $len; + } elseif ($len === 0) { + return null; + } + + $pos = $len - 1; + while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) { + $pos--; + } + } + + return Helpers::falseToNull($pos); + } + + + /** + * Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well. + */ + public static function split( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + bool $skipEmpty = false, + int $limit = -1, + bool $utf8 = false, + ): array + { + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0); + + $pattern .= $utf8 ? 'u' : ''; + $m = self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]); + return $utf8 && $captureOffset + ? self::bytesToChars($subject, [$m])[0] + : $m; + + } + + + /** + * Searches the string for the part matching the regular expression and returns + * an array with the found expression and individual subexpressions, or `null`. + */ + public static function match( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + bool $utf8 = false, + ): ?array + { + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } + + if ($offset > strlen($subject)) { + return null; + } elseif (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) { + return null; + } elseif ($utf8 && $captureOffset) { + return self::bytesToChars($subject, [$m])[0]; + } else { + return $m; + } + } + + + /** + * Searches the string for all occurrences matching the regular expression and + * returns an array of arrays containing the found expression and each subexpression. + * @return ($lazy is true ? \Generator : array[]) + */ + public static function matchAll( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + bool $patternOrder = false, + bool $utf8 = false, + bool $lazy = false, + ): array|\Generator + { + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } + + if ($lazy) { + $flags = PREG_OFFSET_CAPTURE | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + return (function () use ($utf8, $captureOffset, $flags, $subject, $pattern, $offset) { + $counter = 0; + while ( + $offset <= strlen($subject) - ($counter ? 1 : 0) + && self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset]) + ) { + $offset = $m[0][1] + max(1, strlen($m[0][0])); + if (!$captureOffset) { + $m = array_map(fn($item) => $item[0], $m); + } elseif ($utf8) { + $m = self::bytesToChars($subject, [$m])[0]; + } + yield $counter++ => $m; + } + })(); + } + + if ($offset > strlen($subject)) { + return []; + } + + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0); + + self::pcre('preg_match_all', [ + $pattern, $subject, &$m, + ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), + $offset, + ]); + return $utf8 && $captureOffset + ? self::bytesToChars($subject, $m) + : $m; + } + + + /** + * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`. + */ + public static function replace( + string $subject, + #[Language('RegExp')] + string|array $pattern, + string|callable $replacement = '', + int $limit = -1, + bool $captureOffset = false, + bool $unmatchedAsNull = false, + bool $utf8 = false, + ): string + { + if (is_object($replacement) || is_array($replacement)) { + if (!is_callable($replacement, false, $textual)) { + throw new Nette\InvalidStateException("Callback '$textual' is not callable."); + } + + $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + if ($utf8) { + $pattern .= 'u'; + if ($captureOffset) { + $replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]); + } + } + + return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]); + + } elseif (is_array($pattern) && is_string(key($pattern))) { + $replacement = array_values($pattern); + $pattern = array_keys($pattern); + } + + if ($utf8) { + $pattern = array_map(fn($item) => $item . 'u', (array) $pattern); + } + + return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); + } + + + private static function bytesToChars(string $s, array $groups): array + { + $lastBytes = $lastChars = 0; + foreach ($groups as &$matches) { + foreach ($matches as &$match) { + if ($match[1] > $lastBytes) { + $lastChars += self::length(substr($s, $lastBytes, $match[1] - $lastBytes)); + } elseif ($match[1] < $lastBytes) { + $lastChars -= self::length(substr($s, $match[1], $lastBytes - $match[1])); + } + + $lastBytes = $match[1]; + $match[1] = $lastChars; + } + } + + return $groups; + } + + + /** @internal */ + public static function pcre(string $func, array $args) + { + $res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void { + // compile-time error, not detectable by preg_last_error + throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0])); + }); + + if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars + && ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true)) + ) { + throw new RegexpException(preg_last_error_msg() + . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code); + } + + return $res; + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php new file mode 100644 index 0000000..7a17881 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Type.php @@ -0,0 +1,267 @@ + */ + private array $types; + private bool $simple; + private string $kind; // | & + + + /** + * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name. + * If the subject has no type, it returns null. + */ + public static function fromReflection( + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, + ): ?self + { + $type = $reflection instanceof \ReflectionFunctionAbstract + ? $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $reflection instanceof \ReflectionMethod ? $reflection->getTentativeReturnType() : null) + : $reflection->getType(); + + return $type ? self::fromReflectionType($type, $reflection, asObject: true) : null; + } + + + private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string + { + if ($type instanceof \ReflectionNamedType) { + $name = self::resolve($type->getName(), $of); + return $asObject + ? new self($type->allowsNull() && $name !== 'mixed' ? [$name, 'null'] : [$name]) + : $name; + + } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { + return new self( + array_map(fn($t) => self::fromReflectionType($t, $of, asObject: false), $type->getTypes()), + $type instanceof \ReflectionUnionType ? '|' : '&', + ); + + } else { + throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($of)); + } + } + + + /** + * Creates the Type object according to the text notation. + */ + public static function fromString(string $type): self + { + if (!Validators::isTypeDeclaration($type)) { + throw new Nette\InvalidArgumentException("Invalid type '$type'."); + } + + if ($type[0] === '?') { + return new self([substr($type, 1), 'null']); + } + + $unions = []; + foreach (explode('|', $type) as $part) { + $part = explode('&', trim($part, '()')); + $unions[] = count($part) === 1 ? $part[0] : new self($part, '&'); + } + + return count($unions) === 1 && $unions[0] instanceof self + ? $unions[0] + : new self($unions); + } + + + /** + * Resolves 'self', 'static' and 'parent' to the actual class name. + */ + public static function resolve( + string $type, + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of, + ): string + { + $lower = strtolower($type); + if ($of instanceof \ReflectionFunction) { + return $type; + } elseif ($lower === 'self') { + return $of->getDeclaringClass()->name; + } elseif ($lower === 'static') { + return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $of->getDeclaringClass())->name; + } elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) { + return $of->getDeclaringClass()->getParentClass()->name; + } else { + return $type; + } + } + + + private function __construct(array $types, string $kind = '|') + { + $o = array_search('null', $types, strict: true); + if ($o !== false) { // null as last + array_splice($types, $o, 1); + $types[] = 'null'; + } + + $this->types = $types; + $this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null'; + $this->kind = count($types) > 1 ? $kind : ''; + } + + + public function __toString(): string + { + $multi = count($this->types) > 1; + if ($this->simple) { + return ($multi ? '?' : '') . $this->types[0]; + } + + $res = []; + foreach ($this->types as $type) { + $res[] = $type instanceof self && $multi ? "($type)" : $type; + } + return implode($this->kind, $res); + } + + + /** + * Returns the array of subtypes that make up the compound type as strings. + * @return array + */ + public function getNames(): array + { + return array_map(fn($t) => $t instanceof self ? $t->getNames() : $t, $this->types); + } + + + /** + * Returns the array of subtypes that make up the compound type as Type objects: + * @return self[] + */ + public function getTypes(): array + { + return array_map(fn($t) => $t instanceof self ? $t : new self([$t]), $this->types); + } + + + /** + * Returns the type name for simple types, otherwise null. + */ + public function getSingleName(): ?string + { + return $this->simple + ? $this->types[0] + : null; + } + + + /** + * Returns true whether it is a union type. + */ + public function isUnion(): bool + { + return $this->kind === '|'; + } + + + /** + * Returns true whether it is an intersection type. + */ + public function isIntersection(): bool + { + return $this->kind === '&'; + } + + + /** + * Returns true whether it is a simple type. Single nullable types are also considered to be simple types. + */ + public function isSimple(): bool + { + return $this->simple; + } + + + /** @deprecated use isSimple() */ + public function isSingle(): bool + { + return $this->simple; + } + + + /** + * Returns true whether the type is both a simple and a PHP built-in type. + */ + public function isBuiltin(): bool + { + return $this->simple && Validators::isBuiltinType($this->types[0]); + } + + + /** + * Returns true whether the type is both a simple and a class name. + */ + public function isClass(): bool + { + return $this->simple && !Validators::isBuiltinType($this->types[0]); + } + + + /** + * Determines if type is special class name self/parent/static. + */ + public function isClassKeyword(): bool + { + return $this->simple && Validators::isClassKeyword($this->types[0]); + } + + + /** + * Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter. + */ + public function allows(string $subtype): bool + { + if ($this->types === ['mixed']) { + return true; + } + + $subtype = self::fromString($subtype); + return $subtype->isUnion() + ? Arrays::every($subtype->types, fn($t) => $this->allows2($t instanceof self ? $t->types : [$t])) + : $this->allows2($subtype->types); + } + + + private function allows2(array $subtypes): bool + { + return $this->isUnion() + ? Arrays::some($this->types, fn($t) => $this->allows3($t instanceof self ? $t->types : [$t], $subtypes)) + : $this->allows3($this->types, $subtypes); + } + + + private function allows3(array $types, array $subtypes): bool + { + return Arrays::every( + $types, + fn($type) => Arrays::some( + $subtypes, + fn($subtype) => Validators::isBuiltinType($type) + ? strcasecmp($type, $subtype) === 0 + : is_a($subtype, $type, allow_string: true) + ) + ); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php b/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php new file mode 100644 index 0000000..61ccf09 --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/Validators.php @@ -0,0 +1,416 @@ + 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, + 'never' => 1, 'true' => 1, + ]; + + /** @var array */ + protected static $validators = [ + // PHP types + 'array' => 'is_array', + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'float' => 'is_float', + 'int' => 'is_int', + 'integer' => 'is_int', + 'null' => 'is_null', + 'object' => 'is_object', + 'resource' => 'is_resource', + 'scalar' => 'is_scalar', + 'string' => 'is_string', + + // pseudo-types + 'callable' => [self::class, 'isCallable'], + 'iterable' => 'is_iterable', + 'list' => [Arrays::class, 'isList'], + 'mixed' => [self::class, 'isMixed'], + 'none' => [self::class, 'isNone'], + 'number' => [self::class, 'isNumber'], + 'numeric' => [self::class, 'isNumeric'], + 'numericint' => [self::class, 'isNumericInt'], + + // string patterns + 'alnum' => 'ctype_alnum', + 'alpha' => 'ctype_alpha', + 'digit' => 'ctype_digit', + 'lower' => 'ctype_lower', + 'pattern' => null, + 'space' => 'ctype_space', + 'unicode' => [self::class, 'isUnicode'], + 'upper' => 'ctype_upper', + 'xdigit' => 'ctype_xdigit', + + // syntax validation + 'email' => [self::class, 'isEmail'], + 'identifier' => [self::class, 'isPhpIdentifier'], + 'uri' => [self::class, 'isUri'], + 'url' => [self::class, 'isUrl'], + + // environment validation + 'class' => 'class_exists', + 'interface' => 'interface_exists', + 'directory' => 'is_dir', + 'file' => 'is_file', + 'type' => [self::class, 'isType'], + ]; + + /** @var array */ + protected static $counters = [ + 'string' => 'strlen', + 'unicode' => [Strings::class, 'length'], + 'array' => 'count', + 'list' => 'count', + 'alnum' => 'strlen', + 'alpha' => 'strlen', + 'digit' => 'strlen', + 'lower' => 'strlen', + 'space' => 'strlen', + 'upper' => 'strlen', + 'xdigit' => 'strlen', + ]; + + + /** + * Verifies that the value is of expected types separated by pipe. + * @throws AssertionException + */ + public static function assert(mixed $value, string $expected, string $label = 'variable'): void + { + if (!static::is($value, $expected)) { + $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); + $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null']; + $type = $translate[gettype($value)] ?? gettype($value); + if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) { + $type .= ' ' . var_export($value, return: true); + } elseif (is_object($value)) { + $type .= ' ' . $value::class; + } + + throw new AssertionException("The $label expects to be $expected, $type given."); + } + } + + + /** + * Verifies that element $key in array is of expected types separated by pipe. + * @param mixed[] $array + * @throws AssertionException + */ + public static function assertField( + array $array, + $key, + ?string $expected = null, + string $label = "item '%' in array", + ): void + { + if (!array_key_exists($key, $array)) { + throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.'); + + } elseif ($expected) { + static::assert($array[$key], $expected, str_replace('%', $key, $label)); + } + } + + + /** + * Verifies that the value is of expected types separated by pipe. + */ + public static function is(mixed $value, string $expected): bool + { + foreach (explode('|', $expected) as $item) { + if (str_ends_with($item, '[]')) { + if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { + return true; + } + + continue; + } elseif (str_starts_with($item, '?')) { + $item = substr($item, 1); + if ($value === null) { + return true; + } + } + + [$type] = $item = explode(':', $item, 2); + if (isset(static::$validators[$type])) { + try { + if (!static::$validators[$type]($value)) { + continue; + } + } catch (\TypeError $e) { + continue; + } + } elseif ($type === 'pattern') { + if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) { + return true; + } + + continue; + } elseif (!$value instanceof $type) { + continue; + } + + if (isset($item[1])) { + $length = $value; + if (isset(static::$counters[$type])) { + $length = static::$counters[$type]($value); + } + + $range = explode('..', $item[1]); + if (!isset($range[1])) { + $range[1] = $range[0]; + } + + if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) { + continue; + } + } + + return true; + } + + return false; + } + + + /** + * Finds whether all values are of expected types separated by pipe. + * @param mixed[] $values + */ + public static function everyIs(iterable $values, string $expected): bool + { + foreach ($values as $value) { + if (!static::is($value, $expected)) { + return false; + } + } + + return true; + } + + + /** + * Checks if the value is an integer or a float. + * @return ($value is int|float ? true : false) + */ + public static function isNumber(mixed $value): bool + { + return is_int($value) || is_float($value); + } + + + /** + * Checks if the value is an integer or a integer written in a string. + * @return ($value is non-empty-string ? bool : ($value is int ? true : false)) + */ + public static function isNumericInt(mixed $value): bool + { + return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value)); + } + + + /** + * Checks if the value is a number or a number written in a string. + * @return ($value is non-empty-string ? bool : ($value is int|float ? true : false)) + */ + public static function isNumeric(mixed $value): bool + { + return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value)); + } + + + /** + * Checks if the value is a syntactically correct callback. + */ + public static function isCallable(mixed $value): bool + { + return $value && is_callable($value, syntax_only: true); + } + + + /** + * Checks if the value is a valid UTF-8 string. + */ + public static function isUnicode(mixed $value): bool + { + return is_string($value) && preg_match('##u', $value); + } + + + /** + * Checks if the value is 0, '', false or null. + * @return ($value is 0|''|false|null ? true : false) + */ + public static function isNone(mixed $value): bool + { + return $value == null; // intentionally == + } + + + /** @internal */ + public static function isMixed(): bool + { + return true; + } + + + /** + * Checks if a variable is a zero-based integer indexed array. + * @deprecated use Nette\Utils\Arrays::isList + * @return ($value is list ? true : false) + */ + public static function isList(mixed $value): bool + { + return Arrays::isList($value); + } + + + /** + * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null). + * Numbers, strings and DateTime objects can be compared. + */ + public static function isInRange(mixed $value, array $range): bool + { + if ($value === null || !(isset($range[0]) || isset($range[1]))) { + return false; + } + + $limit = $range[0] ?? $range[1]; + if (is_string($limit)) { + $value = (string) $value; + } elseif ($limit instanceof \DateTimeInterface) { + if (!$value instanceof \DateTimeInterface) { + return false; + } + } elseif (is_numeric($value)) { + $value *= 1; + } else { + return false; + } + + return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1])); + } + + + /** + * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. + */ + public static function isEmail(string $value): bool + { + $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part + $alpha = "a-z\x80-\xFF"; // superset of IDN + return (bool) preg_match(<< \\? (? [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) | + (? (?&type) (& (?&type))+ ) | + (? (?&type) | \( (?&intersection) \) ) (\| (?&upart))+ + )$~xAD + XX, $type); + } +} diff --git a/tools/.phpstan/vendor/nette/utils/src/Utils/exceptions.php b/tools/.phpstan/vendor/nette/utils/src/Utils/exceptions.php new file mode 100644 index 0000000..af949ce --- /dev/null +++ b/tools/.phpstan/vendor/nette/utils/src/Utils/exceptions.php @@ -0,0 +1,50 @@ + + array ( + 'install_path' => '/usr/local/src/phpunit/tools/.phpstan/vendor/ergebnis/phpstan-rules', + 'relative_install_path' => '../../../ergebnis/phpstan-rules', + 'extra' => + array ( + 'includes' => + array ( + 0 => 'rules.neon', + ), + ), + 'version' => '2.8.0', + 'phpstanVersionConstraint' => '>=2.0.0.0-dev, <3.0.0.0-dev', + ), + 'phpstan/phpstan-strict-rules' => + array ( + 'install_path' => '/usr/local/src/phpunit/tools/.phpstan/vendor/phpstan/phpstan-strict-rules', + 'relative_install_path' => '../../phpstan-strict-rules', + 'extra' => + array ( + 'includes' => + array ( + 0 => 'rules.neon', + ), + ), + 'version' => '2.0.3', + 'phpstanVersionConstraint' => '>=2.0.4.0-dev, <3.0.0.0-dev', + ), + 'tomasvotruba/type-coverage' => + array ( + 'install_path' => '/usr/local/src/phpunit/tools/.phpstan/vendor/tomasvotruba/type-coverage', + 'relative_install_path' => '../../../tomasvotruba/type-coverage', + 'extra' => + array ( + 'includes' => + array ( + 0 => 'config/extension.neon', + ), + ), + 'version' => '2.0.2', + 'phpstanVersionConstraint' => '>=2.0.0.0-dev, <3.0.0.0-dev', + ), +); + + public const NOT_INSTALLED = array ( +); + + /** @var string|null */ + public const PHPSTAN_VERSION_CONSTRAINT = '>=2.0.4.0-dev, <3.0.0.0-dev'; + + private function __construct() + { + } + +} diff --git a/tools/.phpstan/vendor/phpstan/extension-installer/src/Plugin.php b/tools/.phpstan/vendor/phpstan/extension-installer/src/Plugin.php new file mode 100644 index 0000000..ec75735 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/extension-installer/src/Plugin.php @@ -0,0 +1,228 @@ + + */ + public static function getSubscribedEvents(): array + { + return [ + ScriptEvents::POST_INSTALL_CMD => 'process', + ScriptEvents::POST_UPDATE_CMD => 'process', + ]; + } + + public function process(Event $event): void + { + $io = $event->getIO(); + + if (!file_exists(__DIR__)) { + $io->write('phpstan/extension-installer: Package not found (probably scheduled for removal); extensions installation skipped.'); + return; + } + + $composer = $event->getComposer(); + $installationManager = $composer->getInstallationManager(); + + $generatedConfigFilePath = __DIR__ . '/GeneratedConfig.php'; + $oldGeneratedConfigFileHash = null; + if (is_file($generatedConfigFilePath)) { + $oldGeneratedConfigFileHash = md5_file($generatedConfigFilePath); + } + $notInstalledPackages = []; + $installedPackages = []; + $ignoredPackages = []; + + $data = []; + $fs = new Filesystem(); + $ignore = []; + + $packageExtra = $composer->getPackage()->getExtra(); + + if (isset($packageExtra['phpstan/extension-installer']['ignore'])) { + $ignore = $packageExtra['phpstan/extension-installer']['ignore']; + } + + $phpstanVersionConstraints = []; + + foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $package) { + if ( + $package->getType() !== 'phpstan-extension' + && !isset($package->getExtra()['phpstan']) + ) { + if ( + strpos($package->getName(), 'phpstan') !== false + && !in_array($package->getName(), [ + 'phpstan/phpstan', + 'phpstan/phpstan-shim', + 'phpstan/phpdoc-parser', + 'phpstan/extension-installer', + ], true) + ) { + $notInstalledPackages[$package->getName()] = $package->getFullPrettyVersion(); + } + continue; + } + + if (in_array($package->getName(), $ignore, true)) { + $ignoredPackages[] = $package->getName(); + continue; + } + + $installPath = $installationManager->getInstallPath($package); + if ($installPath === null) { + continue; + } + + $absoluteInstallPath = $fs->isAbsolutePath($installPath) + ? $installPath + : getcwd() . DIRECTORY_SEPARATOR . $installPath; + + $packageRequires = $package->getRequires(); + $phpstanConstraint = null; + if (array_key_exists('phpstan/phpstan', $packageRequires)) { + $phpstanConstraint = $packageRequires['phpstan/phpstan']->getConstraint(); + if ($phpstanConstraint->getLowerBound()->isZero()) { + continue; + } + if ($phpstanConstraint->getUpperBound()->isPositiveInfinity()) { + continue; + } + $phpstanVersionConstraints[] = $phpstanConstraint; + } + + $data[$package->getName()] = [ + 'install_path' => $absoluteInstallPath, + 'relative_install_path' => $fs->findShortestPath(dirname($generatedConfigFilePath), $absoluteInstallPath, true), + 'extra' => $package->getExtra()['phpstan'] ?? null, + 'version' => $package->getFullPrettyVersion(), + 'phpstanVersionConstraint' => $phpstanConstraint !== null ? $this->constraintIntoString($phpstanConstraint) : null, + ]; + + $installedPackages[$package->getName()] = true; + } + + $phpstanVersionConstraint = null; + if (count($phpstanVersionConstraints) > 0 && class_exists(Intervals::class)) { + if (count($phpstanVersionConstraints) === 1) { + $multiConstraint = $phpstanVersionConstraints[0]; + } else { + $multiConstraint = new MultiConstraint($phpstanVersionConstraints); + } + $phpstanVersionConstraint = $this->constraintIntoString(Intervals::compactConstraint($multiConstraint)); + } + + ksort($data); + ksort($installedPackages); + ksort($notInstalledPackages); + sort($ignoredPackages); + + $generatedConfigFileContents = sprintf(self::$generatedFileTemplate, var_export($data, true), var_export($notInstalledPackages, true), var_export($phpstanVersionConstraint, true)); + file_put_contents($generatedConfigFilePath, $generatedConfigFileContents); + $io->write('phpstan/extension-installer: Extensions installed'); + + if ($oldGeneratedConfigFileHash === md5($generatedConfigFileContents)) { + return; + } + + foreach (array_keys($installedPackages) as $name) { + $io->write(sprintf('> %s: installed', $name)); + } + + foreach (array_keys($notInstalledPackages) as $name) { + $io->write(sprintf('> %s: not supported', $name)); + } + + foreach ($ignoredPackages as $name) { + $io->write(sprintf('> %s: ignored', $name)); + } + } + + private function constraintIntoString(ConstraintInterface $constraint): string + { + return sprintf( + '%s%s, %s%s', + $constraint->getLowerBound()->isInclusive() ? '>=' : '>', + $constraint->getLowerBound()->getVersion(), + $constraint->getUpperBound()->isInclusive() ? '<=' : '<', + $constraint->getUpperBound()->getVersion() + ); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/.editorconfig b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/.editorconfig new file mode 100644 index 0000000..5d66bc4 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/.editorconfig @@ -0,0 +1,27 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +[*.{php,phpt}] +indent_style = tab +indent_size = 4 + +[*.xml] +indent_style = tab +indent_size = 4 + +[*.neon] +indent_style = tab +indent_size = 4 + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +[composer.json] +indent_style = tab +indent_size = 4 diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/LICENSE b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/LICENSE new file mode 100644 index 0000000..d005374 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2016 Ondřej Mirtes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md new file mode 100644 index 0000000..14ade92 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/README.md @@ -0,0 +1,108 @@ +# Extra strict and opinionated rules for PHPStan + +[![Build](https://github.com/phpstan/phpstan-strict-rules/workflows/Build/badge.svg)](https://github.com/phpstan/phpstan-strict-rules/actions) +[![Latest Stable Version](https://poser.pugx.org/phpstan/phpstan-strict-rules/v/stable)](https://packagist.org/packages/phpstan/phpstan-strict-rules) +[![License](https://poser.pugx.org/phpstan/phpstan-strict-rules/license)](https://packagist.org/packages/phpstan/phpstan-strict-rules) + +[PHPStan](https://phpstan.org/) focuses on finding bugs in your code. But in PHP there's a lot of leeway in how stuff can be written. This repository contains additional rules that revolve around strictly and strongly typed code with no loose casting for those who want additional safety in extremely defensive programming: + +* Require booleans in `if`, `elseif`, ternary operator, after `!`, and on both sides of `&&` and `||`. +* Require numeric operands or arrays in `+` and numeric operands in `-`/`*`/`/`/`**`/`%`. +* Require numeric operand in `$var++`, `$var--`, `++$var`and `--$var`. +* These functions contain a `$strict` parameter for better type safety, it must be set to `true`: + * `in_array` (3rd parameter) + * `array_search` (3rd parameter) + * `array_keys` (3rd parameter; only if the 2nd parameter `$search_value` is provided) + * `base64_decode` (2nd parameter) +* Variables assigned in `while` loop condition and `for` loop initial assignment cannot be used after the loop. +* Variables set in foreach that's always looped thanks to non-empty arrays cannot be used after the loop. +* Types in `switch` condition and `case` value must match. PHP compares them loosely by default and that can lead to unexpected results. +* Check that statically declared methods are called statically. +* Disallow `empty()` - it's a very loose comparison (see [manual](https://php.net/empty)), it's recommended to use more strict one. +* Disallow short ternary operator (`?:`) - implies weak comparison, it's recommended to use null coalesce operator (`??`) or ternary operator with strict condition. +* Disallow variable variables (`$$foo`, `$this->$method()` etc.) +* Disallow overwriting variables with foreach key and value variables +* Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`/`checkAlwaysTrueCheckTypeFunctionCall`/`checkAlwaysTrueStrictComparison` to false. +* Correct case for referenced and called function names. +* Correct case for inherited and implemented method names. +* Contravariance for parameter types and covariance for return types in inherited methods (also known as Liskov substitution principle - LSP) +* Check LSP even for static methods +* Require calling parent constructor +* Disallow usage of backtick operator (`` $ls = `ls -la` ``) +* Closure should use `$this` directly instead of using `$this` variable indirectly + +Additional rules are coming in subsequent releases! + + +## Installation + +To use this extension, require it in [Composer](https://getcomposer.org/): + +``` +composer require --dev phpstan/phpstan-strict-rules +``` + +If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set! + +
+ Manual installation + +If you don't want to use `phpstan/extension-installer`, include rules.neon in your project's PHPStan config: + +``` +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon +``` +
+ +## Disabling rules + +You can disable rules using configuration parameters: + +```neon +parameters: + strictRules: + disallowedLooseComparison: false + booleansInConditions: false + uselessCast: false + requireParentConstructorCall: false + disallowedBacktick: false + disallowedEmpty: false + disallowedImplicitArrayCreation: false + disallowedShortTernary: false + overwriteVariablesWithLoop: false + closureUsesThis: false + matchingInheritedMethodNames: false + numericOperandsInArithmeticOperators: false + strictFunctionCalls: false + dynamicCallOnStaticMethod: false + switchConditionsMatchingType: false + noVariableVariables: false + strictArrayFilter: false + illegalConstructorMethodCall: false +``` + +Aside from introducing new custom rules, phpstan-strict-rules also [change the default values of some configuration parameters](https://github.com/phpstan/phpstan-strict-rules/blob/1.6.x/rules.neon#L1) that are present in PHPStan itself. These parameters are [documented on phpstan.org](https://phpstan.org/config-reference#stricter-analysis). + +## Enabling rules one-by-one + +If you don't want to start using all the available strict rules at once but only one or two, you can! + +You can disable all rules from the included `rules.neon` with: + +```neon +parameters: + strictRules: + allRules: false +``` + +Then you can re-enable individual rules with configuration parameters: + +```neon +parameters: + strictRules: + allRules: false + booleansInConditions: true +``` + +Even with `strictRules.allRules` set to `false`, part of this package is still in effect. That's because phpstan-strict-rules also [change the default values of some configuration parameters](https://github.com/phpstan/phpstan-strict-rules/blob/1.6.x/rules.neon#L1) that are present in PHPStan itself. These parameters are [documented on phpstan.org](https://phpstan.org/config-reference#stricter-analysis). diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json new file mode 100644 index 0000000..2bbc44d --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/composer.json @@ -0,0 +1,43 @@ +{ + "name": "phpstan/phpstan-strict-rules", + "type": "phpstan-extension", + "description": "Extra strict and opinionated rules for PHPStan", + "license": [ + "MIT" + ], + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "config": { + "platform": { + "php": "7.4.6" + }, + "sort-packages": true + }, + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon new file mode 100644 index 0000000..61b4985 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/rules.neon @@ -0,0 +1,279 @@ +parameters: + strictRulesInstalled: true + polluteScopeWithLoopInitialAssignments: false + polluteScopeWithAlwaysIterableForeach: false + polluteScopeWithBlock: false + checkDynamicProperties: true + checkExplicitMixedMissingReturn: true + checkFunctionNameCase: true + checkInternalClassCaseSensitivity: true + reportMaybesInMethodSignatures: true + reportStaticMethodSignatures: true + reportMaybesInPropertyPhpDocTypes: true + reportWrongPhpDocTypeInVarTag: true + strictRules: + allRules: true + disallowedLooseComparison: %strictRules.allRules% + booleansInConditions: %strictRules.allRules% + uselessCast: %strictRules.allRules% + requireParentConstructorCall: %strictRules.allRules% + disallowedBacktick: %strictRules.allRules% + disallowedEmpty: %strictRules.allRules% + disallowedImplicitArrayCreation: %strictRules.allRules% + disallowedShortTernary: %strictRules.allRules% + overwriteVariablesWithLoop: %strictRules.allRules% + closureUsesThis: %strictRules.allRules% + matchingInheritedMethodNames: %strictRules.allRules% + numericOperandsInArithmeticOperators: %strictRules.allRules% + strictFunctionCalls: %strictRules.allRules% + dynamicCallOnStaticMethod: %strictRules.allRules% + switchConditionsMatchingType: %strictRules.allRules% + noVariableVariables: %strictRules.allRules% + strictArrayFilter: %strictRules.allRules% + illegalConstructorMethodCall: %strictRules.allRules% + +parametersSchema: + strictRules: structure([ + allRules: anyOf(bool(), arrayOf(bool())), + disallowedLooseComparison: anyOf(bool(), arrayOf(bool())), + booleansInConditions: anyOf(bool(), arrayOf(bool())) + uselessCast: anyOf(bool(), arrayOf(bool())) + requireParentConstructorCall: anyOf(bool(), arrayOf(bool())) + disallowedBacktick: anyOf(bool(), arrayOf(bool())) + disallowedEmpty: anyOf(bool(), arrayOf(bool())) + disallowedImplicitArrayCreation: anyOf(bool(), arrayOf(bool())) + disallowedShortTernary: anyOf(bool(), arrayOf(bool())) + overwriteVariablesWithLoop: anyOf(bool(), arrayOf(bool())) + closureUsesThis: anyOf(bool(), arrayOf(bool())) + matchingInheritedMethodNames: anyOf(bool(), arrayOf(bool())) + numericOperandsInArithmeticOperators: anyOf(bool(), arrayOf(bool())) + strictFunctionCalls: anyOf(bool(), arrayOf(bool())) + dynamicCallOnStaticMethod: anyOf(bool(), arrayOf(bool())) + switchConditionsMatchingType: anyOf(bool(), arrayOf(bool())) + noVariableVariables: anyOf(bool(), arrayOf(bool())) + strictArrayFilter: anyOf(bool(), arrayOf(bool())) + illegalConstructorMethodCall: anyOf(bool(), arrayOf(bool())) + ]) + +conditionalTags: + PHPStan\Rules\DisallowedConstructs\DisallowedLooseComparisonRule: + phpstan.rules.rule: %strictRules.disallowedLooseComparison% + PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule: + phpstan.rules.rule: %strictRules.booleansInConditions% + PHPStan\Rules\Cast\UselessCastRule: + phpstan.rules.rule: %strictRules.uselessCast% + PHPStan\Rules\Classes\RequireParentConstructCallRule: + phpstan.rules.rule: %strictRules.requireParentConstructorCall% + PHPStan\Rules\DisallowedConstructs\DisallowedBacktickRule: + phpstan.rules.rule: %strictRules.disallowedBacktick% + PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule: + phpstan.rules.rule: %strictRules.disallowedEmpty% + PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule: + phpstan.rules.rule: %strictRules.disallowedImplicitArrayCreation% + PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule: + phpstan.rules.rule: %strictRules.disallowedShortTernary% + PHPStan\Rules\ForeachLoop\OverwriteVariablesWithForeachRule: + phpstan.rules.rule: %strictRules.overwriteVariablesWithLoop% + PHPStan\Rules\ForLoop\OverwriteVariablesWithForLoopInitRule: + phpstan.rules.rule: %strictRules.overwriteVariablesWithLoop% + PHPStan\Rules\Functions\ArrayFilterStrictRule: + phpstan.rules.rule: %strictRules.strictArrayFilter% + PHPStan\Rules\Functions\ClosureUsesThisRule: + phpstan.rules.rule: %strictRules.closureUsesThis% + PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule: + phpstan.rules.rule: %strictRules.matchingInheritedMethodNames% + PHPStan\Rules\Operators\OperandInArithmeticPostDecrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandInArithmeticPostIncrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandInArithmeticPreDecrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticExponentiationRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticModuloRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule: + phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% + PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule: + phpstan.rules.rule: %strictRules.dynamicCallOnStaticMethod% + PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsCallableRule: + phpstan.rules.rule: %strictRules.dynamicCallOnStaticMethod% + PHPStan\Rules\StrictCalls\StrictFunctionCallsRule: + phpstan.rules.rule: %strictRules.strictFunctionCalls% + PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule: + phpstan.rules.rule: %strictRules.switchConditionsMatchingType% + PHPStan\Rules\VariableVariables\VariableMethodCallRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableMethodCallableRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableStaticMethodCallRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableStaticMethodCallableRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableStaticPropertyFetchRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariableVariablesRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\VariableVariables\VariablePropertyFetchRule: + phpstan.rules.rule: %strictRules.noVariableVariables% + PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: + phpstan.rules.rule: %strictRules.illegalConstructorMethodCall% + PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: + phpstan.rules.rule: %strictRules.illegalConstructorMethodCall% + +services: + - + class: PHPStan\Rules\BooleansInConditions\BooleanRuleHelper + + - + class: PHPStan\Rules\Operators\OperatorRuleHelper + + - + class: PHPStan\Rules\VariableVariables\VariablePropertyFetchRule + arguments: + universalObjectCratesClasses: %universalObjectCratesClasses% + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedLooseComparisonRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule + + - + class: PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule + + - + class: PHPStan\Rules\Cast\UselessCastRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + + - + class: PHPStan\Rules\Classes\RequireParentConstructCallRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedBacktickRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule + + - + class: PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule + + - + class: PHPStan\Rules\ForeachLoop\OverwriteVariablesWithForeachRule + + - + class: PHPStan\Rules\ForLoop\OverwriteVariablesWithForLoopInitRule + + - + class: PHPStan\Rules\Functions\ArrayFilterStrictRule + arguments: + treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% + checkNullables: %checkNullables% + treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% + + - + class: PHPStan\Rules\Functions\ClosureUsesThisRule + + - + class: PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule + + - + class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule + + - + class: PHPStan\Rules\Methods\IllegalConstructorStaticCallRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPostDecrementRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPostIncrementRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPreDecrementRule + + - + class: PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticExponentiationRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticModuloRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule + + - + class: PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule + + - + class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule + + - + class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsCallableRule + + - + class: PHPStan\Rules\StrictCalls\StrictFunctionCallsRule + + - + class: PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule + + - + class: PHPStan\Rules\VariableVariables\VariableMethodCallRule + + - + class: PHPStan\Rules\VariableVariables\VariableMethodCallableRule + + - + class: PHPStan\Rules\VariableVariables\VariableStaticMethodCallRule + + - + class: PHPStan\Rules\VariableVariables\VariableStaticMethodCallableRule + + - + class: PHPStan\Rules\VariableVariables\VariableStaticPropertyFetchRule + + - + class: PHPStan\Rules\VariableVariables\VariableVariablesRule diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php new file mode 100644 index 0000000..eed19ae --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php @@ -0,0 +1,59 @@ + + */ +class BooleanInBooleanAndRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return BooleanAndNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $originalNode = $node->getOriginalNode(); + $messages = []; + $nodeText = $originalNode->getOperatorSigil(); + $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanAnd ? 'booleanAnd' : 'logicalAnd'; + if (!$this->helper->passesAsBoolean($scope, $originalNode->left)) { + $leftType = $scope->getType($originalNode->left); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the left side.', + $nodeText, + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.leftNotBoolean', $identifierType))->build(); + } + + $rightScope = $node->getRightScope(); + if (!$this->helper->passesAsBoolean($rightScope, $originalNode->right)) { + $rightType = $rightScope->getType($originalNode->right); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the right side.', + $nodeText, + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.rightNotBoolean', $identifierType))->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php new file mode 100644 index 0000000..5187cf5 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php @@ -0,0 +1,47 @@ + + */ +class BooleanInBooleanNotRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return BooleanNot::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->expr)) { + return []; + } + + $expressionType = $scope->getType($node->expr); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in a negated boolean, %s given.', + $expressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('booleanNot.exprNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php new file mode 100644 index 0000000..cb06a34 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php @@ -0,0 +1,59 @@ + + */ +class BooleanInBooleanOrRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return BooleanOrNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $originalNode = $node->getOriginalNode(); + $messages = []; + $nodeText = $originalNode->getOperatorSigil(); + $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanOr ? 'booleanOr' : 'logicalOr'; + if (!$this->helper->passesAsBoolean($scope, $originalNode->left)) { + $leftType = $scope->getType($originalNode->left); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the left side.', + $nodeText, + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.leftNotBoolean', $identifierType))->build(); + } + + $rightScope = $node->getRightScope(); + if (!$this->helper->passesAsBoolean($rightScope, $originalNode->right)) { + $rightType = $rightScope->getType($originalNode->right); + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in %s, %s given on the right side.', + $nodeText, + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.rightNotBoolean', $identifierType))->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php new file mode 100644 index 0000000..550e985 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php @@ -0,0 +1,47 @@ + + */ +class BooleanInElseIfConditionRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return ElseIf_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in an elseif condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('elseif.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php new file mode 100644 index 0000000..5c08894 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php @@ -0,0 +1,47 @@ + + */ +class BooleanInIfConditionRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return If_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in an if condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('if.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php new file mode 100644 index 0000000..4fe855a --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php @@ -0,0 +1,51 @@ + + */ +class BooleanInTernaryOperatorRule implements Rule +{ + + private BooleanRuleHelper $helper; + + public function __construct(BooleanRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Ternary::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->if === null) { + return []; // elvis ?: + } + + if ($this->helper->passesAsBoolean($scope, $node->cond)) { + return []; + } + + $conditionExpressionType = $scope->getType($node->cond); + + return [ + RuleErrorBuilder::message(sprintf( + 'Only booleans are allowed in a ternary operator condition, %s given.', + $conditionExpressionType->describe(VerbosityLevel::typeOnly()), + ))->identifier('ternary.condNotBoolean')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanRuleHelper.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanRuleHelper.php new file mode 100644 index 0000000..4ecba32 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanRuleHelper.php @@ -0,0 +1,42 @@ +ruleLevelHelper = $ruleLevelHelper; + } + + public function passesAsBoolean(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return !$type->isExplicitMixed(); + } + $typeToCheck = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static fn (Type $type): bool => $type->isBoolean()->yes(), + ); + $foundType = $typeToCheck->getType(); + if ($foundType instanceof ErrorType) { + return true; + } + + return $foundType->isBoolean()->yes(); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Cast/UselessCastRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Cast/UselessCastRule.php new file mode 100644 index 0000000..ca8f226 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Cast/UselessCastRule.php @@ -0,0 +1,81 @@ + + */ +class UselessCastRule implements Rule +{ + + private bool $treatPhpDocTypesAsCertain; + + private bool $treatPhpDocTypesAsCertainTip; + + public function __construct( + bool $treatPhpDocTypesAsCertain, + bool $treatPhpDocTypesAsCertainTip + ) + { + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->treatPhpDocTypesAsCertainTip = $treatPhpDocTypesAsCertainTip; + } + + public function getNodeType(): string + { + return Cast::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $castType = $scope->getType($node); + if ($castType instanceof ErrorType) { + return []; + } + $castType = $castType->generalize(GeneralizePrecision::lessSpecific()); + + if ($this->treatPhpDocTypesAsCertain) { + $expressionType = $scope->getType($node->expr); + } else { + $expressionType = $scope->getNativeType($node->expr); + } + if ($castType->isSuperTypeOf($expressionType)->yes()) { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $castType): RuleErrorBuilder { + if (!$this->treatPhpDocTypesAsCertain) { + return $ruleErrorBuilder; + } + + $expressionTypeWithoutPhpDoc = $scope->getNativeType($node->expr); + if ($castType->isSuperTypeOf($expressionTypeWithoutPhpDoc)->yes()) { + return $ruleErrorBuilder; + } + + if (!$this->treatPhpDocTypesAsCertainTip) { + return $ruleErrorBuilder; + } + + return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); + }; + return [ + $addTip(RuleErrorBuilder::message(sprintf( + 'Casting to %s something that\'s already %s.', + $castType->describe(VerbosityLevel::typeOnly()), + $expressionType->describe(VerbosityLevel::typeOnly()), + )))->identifier('cast.useless')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php new file mode 100644 index 0000000..7759581 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Classes/RequireParentConstructCallRule.php @@ -0,0 +1,142 @@ + + */ +class RequireParentConstructCallRule implements Rule +{ + + public function getNodeType(): string + { + return ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$scope->isInClass()) { + throw new ShouldNotHappenException(); + } + + if ($scope->isInTrait()) { + return []; + } + + if ($node->name->name !== '__construct') { + return []; + } + + if ($node->isAbstract()) { + return []; + } + + $classReflection = $scope->getClassReflection()->getNativeReflection(); + if ($classReflection->isInterface() || $classReflection->isAnonymous()) { + return []; + } + + if ($this->callsParentConstruct($node)) { + return []; + } + + $parentClass = $this->getParentConstructorClass($classReflection); + if ($parentClass !== false) { + return [ + RuleErrorBuilder::message(sprintf( + '%s::__construct() does not call parent constructor from %s.', + $classReflection->getName(), + $parentClass->getName(), + ))->identifier('constructor.missingParentCall')->build(), + ]; + } + + return []; + } + + private function callsParentConstruct(Node $parserNode): bool + { + if (!property_exists($parserNode, 'stmts')) { + return false; + } + + foreach ($parserNode->stmts as $statement) { + if ($statement instanceof Node\Stmt\Expression) { + $statement = $statement->expr; + } + + $statement = $this->ignoreErrorSuppression($statement); + if ($statement instanceof StaticCall) { + if ( + $statement->class instanceof Name + && ((string) $statement->class === 'parent') + && $statement->name instanceof Node\Identifier + && $statement->name->name === '__construct' + ) { + return true; + } + } else { + if ($this->callsParentConstruct($statement)) { + return true; + } + } + } + + return false; + } + + /** + * @param ReflectionClass|ReflectionEnum $classReflection + * @return ReflectionClass|false + */ + private function getParentConstructorClass($classReflection) + { + while ($classReflection->getParentClass() !== false) { + $constructor = $classReflection->getParentClass()->hasMethod('__construct') ? $classReflection->getParentClass()->getMethod('__construct') : null; + $constructorWithClassName = $classReflection->getParentClass()->hasMethod($classReflection->getParentClass()->getName()) ? $classReflection->getParentClass()->getMethod($classReflection->getParentClass()->getName()) : null; + if ( + ( + $constructor !== null + && $constructor->getDeclaringClass()->getName() === $classReflection->getParentClass()->getName() + && !$constructor->isAbstract() + && !$constructor->isPrivate() + && !$constructor->isDeprecated() + ) || ( + $constructorWithClassName !== null + && $constructorWithClassName->getDeclaringClass()->getName() === $classReflection->getParentClass()->getName() + && !$constructorWithClassName->isAbstract() + ) + ) { + return $classReflection->getParentClass(); + } + + $classReflection = $classReflection->getParentClass(); + } + + return false; + } + + private function ignoreErrorSuppression(Node $statement): Node + { + if ($statement instanceof Node\Expr\ErrorSuppress) { + + return $statement->expr; + } + + return $statement; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedBacktickRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedBacktickRule.php new file mode 100644 index 0000000..76e401c --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedBacktickRule.php @@ -0,0 +1,31 @@ + + */ +class DisallowedBacktickRule implements Rule +{ + + public function getNodeType(): string + { + return ShellExec::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return [ + RuleErrorBuilder::message('Backtick operator is not allowed. Use shell_exec() instead.') + ->identifier('backtick.notAllowed') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedEmptyRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedEmptyRule.php new file mode 100644 index 0000000..d19f5ea --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedEmptyRule.php @@ -0,0 +1,31 @@ + + */ +class DisallowedEmptyRule implements Rule +{ + + public function getNodeType(): string + { + return Empty_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + return [ + RuleErrorBuilder::message('Construct empty() is not allowed. Use more strict comparison.') + ->identifier('empty.notAllowed') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedImplicitArrayCreationRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedImplicitArrayCreationRule.php new file mode 100644 index 0000000..cee777c --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedImplicitArrayCreationRule.php @@ -0,0 +1,65 @@ + + */ +class DisallowedImplicitArrayCreationRule implements Rule +{ + + public function getNodeType(): string + { + return Assign::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->var instanceof ArrayDimFetch) { + return []; + } + + $node = $node->var; + while ($node instanceof ArrayDimFetch) { + $node = $node->var; + } + + if (!$node instanceof Variable) { + return []; + } + + if (!is_string($node->name)) { + return []; + } + + $certainty = $scope->hasVariableType($node->name); + if ($certainty->no()) { + return [ + RuleErrorBuilder::message(sprintf('Implicit array creation is not allowed - variable $%s does not exist.', $node->name)) + ->identifier('variable.implicitArray') + ->build(), + ]; + } + + if ($certainty->maybe()) { + return [ + RuleErrorBuilder::message(sprintf('Implicit array creation is not allowed - variable $%s might not exist.', $node->name)) + ->identifier('variable.implicitArray') + ->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedLooseComparisonRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedLooseComparisonRule.php new file mode 100644 index 0000000..70b8514 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedLooseComparisonRule.php @@ -0,0 +1,48 @@ + + */ +class DisallowedLooseComparisonRule implements Rule +{ + + public function getNodeType(): string + { + return BinaryOp::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof Equal) { + return [ + RuleErrorBuilder::message( + 'Loose comparison via "==" is not allowed.', + )->tip('Use strict comparison via "===" instead.') + ->identifier('equal.notAllowed') + ->build(), + ]; + } + if ($node instanceof NotEqual) { + return [ + RuleErrorBuilder::message( + 'Loose comparison via "!=" is not allowed.', + )->tip('Use strict comparison via "!==" instead.') + ->identifier('notEqual.notAllowed') + ->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php new file mode 100644 index 0000000..fac4279 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php @@ -0,0 +1,35 @@ + + */ +class DisallowedShortTernaryRule implements Rule +{ + + public function getNodeType(): string + { + return Ternary::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->if !== null) { + return []; + } + + return [ + RuleErrorBuilder::message('Short ternary operator is not allowed. Use null coalesce operator if applicable or consider using long ternary.') + ->identifier('ternary.shortNotAllowed') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForLoop/OverwriteVariablesWithForLoopInitRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForLoop/OverwriteVariablesWithForLoopInitRule.php new file mode 100644 index 0000000..f710474 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForLoop/OverwriteVariablesWithForLoopInitRule.php @@ -0,0 +1,77 @@ + + */ +class OverwriteVariablesWithForLoopInitRule implements Rule +{ + + public function getNodeType(): string + { + return For_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($node->init as $expr) { + if (!($expr instanceof Assign)) { + continue; + } + + foreach ($this->checkValueVar($scope, $expr->var) as $error) { + $errors[] = $error; + } + } + + return $errors; + } + + /** + * @return list + */ + private function checkValueVar(Scope $scope, Expr $expr): array + { + $errors = []; + if ( + $expr instanceof Node\Expr\Variable + && is_string($expr->name) + && $scope->hasVariableType($expr->name)->yes() + ) { + $errors[] = RuleErrorBuilder::message(sprintf('For loop initial assignment overwrites variable $%s.', $expr->name)) + ->identifier('for.variableOverwrite') + ->build(); + } + + if ( + $expr instanceof Node\Expr\List_ + || $expr instanceof Node\Expr\Array_ + ) { + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + foreach ($this->checkValueVar($scope, $item->value) as $error) { + $errors[] = $error; + } + } + } + + return $errors; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForeachLoop/OverwriteVariablesWithForeachRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForeachLoop/OverwriteVariablesWithForeachRule.php new file mode 100644 index 0000000..0cf620c --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/ForeachLoop/OverwriteVariablesWithForeachRule.php @@ -0,0 +1,80 @@ + + */ +class OverwriteVariablesWithForeachRule implements Rule +{ + + public function getNodeType(): string + { + return Foreach_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + if ( + $node->keyVar instanceof Node\Expr\Variable + && is_string($node->keyVar->name) + && $scope->hasVariableType($node->keyVar->name)->yes() + ) { + $errors[] = RuleErrorBuilder::message(sprintf('Foreach overwrites $%s with its key variable.', $node->keyVar->name)) + ->identifier('foreach.keyOverwrite') + ->build(); + } + + foreach ($this->checkValueVar($scope, $node->valueVar) as $error) { + $errors[] = $error; + } + + return $errors; + } + + /** + * @return list + */ + private function checkValueVar(Scope $scope, Expr $expr): array + { + $errors = []; + if ( + $expr instanceof Node\Expr\Variable + && is_string($expr->name) + && $scope->hasVariableType($expr->name)->yes() + ) { + $errors[] = RuleErrorBuilder::message(sprintf('Foreach overwrites $%s with its value variable.', $expr->name)) + ->identifier('foreach.valueOverwrite') + ->build(); + } + + if ( + $expr instanceof Node\Expr\List_ + || $expr instanceof Node\Expr\Array_ + ) { + foreach ($expr->items as $item) { + if ($item === null) { + continue; + } + + foreach ($this->checkValueVar($scope, $item->value) as $error) { + $errors[] = $error; + } + } + } + + return $errors; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ArrayFilterStrictRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ArrayFilterStrictRule.php new file mode 100644 index 0000000..6760c7d --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ArrayFilterStrictRule.php @@ -0,0 +1,162 @@ + + */ +class ArrayFilterStrictRule implements Rule +{ + + private ReflectionProvider $reflectionProvider; + + private bool $treatPhpDocTypesAsCertain; + + private bool $checkNullables; + + private bool $treatPhpDocTypesAsCertainTip; + + public function __construct( + ReflectionProvider $reflectionProvider, + bool $treatPhpDocTypesAsCertain, + bool $checkNullables, + bool $treatPhpDocTypesAsCertainTip + ) + { + $this->reflectionProvider = $reflectionProvider; + $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; + $this->checkNullables = $checkNullables; + $this->treatPhpDocTypesAsCertainTip = $treatPhpDocTypesAsCertainTip; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Name) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); + + if ($functionReflection->getName() !== 'array_filter') { + return []; + } + + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $node->getArgs(), + $functionReflection->getVariants(), + $functionReflection->getNamedArgumentsVariants(), + ); + + $normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); + + if ($normalizedFuncCall === null) { + return []; + } + + $args = $normalizedFuncCall->getArgs(); + if (count($args) === 0) { + return []; + } + + if (count($args) === 1) { + $arrayType = $scope->getType($args[0]->value); + $itemType = $arrayType->getIterableValueType(); + if ($itemType instanceof UnionType) { + $hasTruthy = false; + $hasFalsey = false; + foreach ($itemType->getTypes() as $innerType) { + $booleanType = $innerType->toBoolean(); + if ($booleanType->isTrue()->yes()) { + $hasTruthy = true; + continue; + } + if ($booleanType->isFalse()->yes()) { + $hasFalsey = true; + continue; + } + + $hasTruthy = false; + $hasFalsey = false; + break; + } + + if ($hasTruthy && $hasFalsey) { + return []; + } + } elseif ($itemType->isBoolean()->yes()) { + return []; + } elseif ($itemType->isArray()->yes()) { + return []; + } + + return [ + RuleErrorBuilder::message('Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.') + ->identifier('arrayFilter.strict') + ->build(), + ]; + } + + $nativeCallbackType = $scope->getNativeType($args[1]->value); + + if ($this->treatPhpDocTypesAsCertain) { + $callbackType = $scope->getType($args[1]->value); + } else { + $callbackType = $nativeCallbackType; + } + + if ($this->isCallbackTypeNull($callbackType)) { + $message = 'Parameter #2 of array_filter() cannot be null to avoid loose comparison semantics (%s given).'; + $errorBuilder = RuleErrorBuilder::message(sprintf( + $message, + $callbackType->describe(VerbosityLevel::typeOnly()), + ))->identifier('arrayFilter.strict'); + + if ($this->treatPhpDocTypesAsCertainTip && !$this->isCallbackTypeNull($nativeCallbackType) && $this->treatPhpDocTypesAsCertain) { + $errorBuilder->treatPhpDocTypesAsCertainTip(); + } + + return [$errorBuilder->build()]; + } + + return []; + } + + private function isCallbackTypeNull(Type $callbackType): bool + { + if ($callbackType->isNull()->yes()) { + return true; + } + + if ($callbackType->isNull()->no()) { + return false; + } + + return $this->checkNullables; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ClosureUsesThisRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ClosureUsesThisRule.php new file mode 100644 index 0000000..4f41d26 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Functions/ClosureUsesThisRule.php @@ -0,0 +1,52 @@ + + */ +class ClosureUsesThisRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\Closure::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->static) { + return []; + } + + if ($scope->isInClosureBind()) { + return []; + } + + $messages = []; + foreach ($node->uses as $closureUse) { + $varType = $scope->getType($closureUse->var); + if (!is_string($closureUse->var->name)) { + continue; + } + if (!$varType instanceof ThisType) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf('Anonymous function uses $this assigned to variable $%s. Use $this directly in the function body.', $closureUse->var->name)) + ->line($closureUse->getStartLine()) + ->identifier('closure.useThis') + ->build(); + } + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorMethodCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorMethodCallRule.php new file mode 100644 index 0000000..1dba6ed --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorMethodCallRule.php @@ -0,0 +1,34 @@ + + */ +final class IllegalConstructorMethodCallRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { + return []; + } + + return [ + RuleErrorBuilder::message('Call to __construct() on an existing object is not allowed.') + ->identifier('constructor.call') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorStaticCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorStaticCallRule.php new file mode 100644 index 0000000..fa747d6 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/IllegalConstructorStaticCallRule.php @@ -0,0 +1,92 @@ + + */ +final class IllegalConstructorStaticCallRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Expr\StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { + return []; + } + + if ($this->isCollectCallingConstructor($node, $scope)) { + return []; + } + + return [ + RuleErrorBuilder::message('Static call to __construct() is only allowed on a parent class in the constructor.') + ->identifier('constructor.call') + ->build(), + ]; + } + + private function isCollectCallingConstructor(Node\Expr\StaticCall $node, Scope $scope): bool + { + // __construct should be called from inside constructor + if ($scope->getFunction() === null) { + return false; + } + + if ($scope->getFunction()->getName() !== '__construct') { + if (!$this->isInRenamedTraitConstructor($scope)) { + return false; + } + } + + if (!$scope->isInClass()) { + return false; + } + + if (!$node->class instanceof Node\Name) { + return false; + } + + $parentClasses = array_map(static fn (string $name) => strtolower($name), $scope->getClassReflection()->getParentClassesNames()); + + return in_array(strtolower($scope->resolveName($node->class)), $parentClasses, true); + } + + private function isInRenamedTraitConstructor(Scope $scope): bool + { + if (!$scope->isInClass()) { + return false; + } + + if (!$scope->isInTrait()) { + return false; + } + + if ($scope->getFunction() === null) { + return false; + } + + $traitAliases = $scope->getClassReflection()->getNativeReflection()->getTraitAliases(); + $functionName = $scope->getFunction()->getName(); + if (!array_key_exists($functionName, $traitAliases)) { + return false; + } + + return $traitAliases[$functionName] === sprintf('%s::%s', $scope->getTraitReflection()->getName(), '__construct'); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/WrongCaseOfInheritedMethodRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/WrongCaseOfInheritedMethodRule.php new file mode 100644 index 0000000..5f800e5 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Methods/WrongCaseOfInheritedMethodRule.php @@ -0,0 +1,86 @@ + + */ +class WrongCaseOfInheritedMethodRule implements Rule +{ + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode( + Node $node, + Scope $scope + ): array + { + $methodReflection = $node->getMethodReflection(); + $declaringClass = $methodReflection->getDeclaringClass(); + + $messages = []; + if ($declaringClass->getParentClass() !== null) { + $parentMessage = $this->findMethod( + $declaringClass, + $declaringClass->getParentClass(), + $methodReflection->getName(), + ); + if ($parentMessage !== null) { + $messages[] = $parentMessage; + } + } + + foreach ($declaringClass->getInterfaces() as $interface) { + $interfaceMessage = $this->findMethod( + $declaringClass, + $interface, + $methodReflection->getName(), + ); + if ($interfaceMessage === null) { + continue; + } + + $messages[] = $interfaceMessage; + } + + return $messages; + } + + private function findMethod( + ClassReflection $declaringClass, + ClassReflection $classReflection, + string $methodName + ): ?IdentifierRuleError + { + if (!$classReflection->hasNativeMethod($methodName)) { + return null; + } + + $parentMethod = $classReflection->getNativeMethod($methodName); + if ($parentMethod->getName() === $methodName) { + return null; + } + + return RuleErrorBuilder::message(sprintf( + 'Method %s::%s() does not match %s method name: %s::%s().', + $declaringClass->getDisplayName(), + $methodName, + $classReflection->isInterface() ? 'interface' : 'parent', + $classReflection->getDisplayName(), + $parentMethod->getName(), + ))->identifier('method.nameCase')->build(); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticIncrementOrDecrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticIncrementOrDecrementRule.php new file mode 100644 index 0000000..4e87a88 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticIncrementOrDecrementRule.php @@ -0,0 +1,61 @@ + + */ +abstract class OperandInArithmeticIncrementOrDecrementRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + /** + * @param TNodeType $node + */ + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + $varType = $scope->getType($node->var); + + if ( + ($node instanceof PreInc || $node instanceof PostInc) + && !$this->helper->isValidForIncrement($scope, $node->var) + || ($node instanceof PreDec || $node instanceof PostDec) + && !$this->helper->isValidForDecrement($scope, $node->var) + ) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in %s, %s given.', + $this->describeOperation(), + $varType->describe(VerbosityLevel::typeOnly()), + ))->identifier(sprintf('%s.nonNumeric', $this->getIdentifier()))->build(); + } + + return $messages; + } + + abstract protected function describeOperation(): string; + + /** + * @return 'preInc'|'postInc'|'preDec'|'postDec' + */ + abstract protected function getIdentifier(): string; + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostDecrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostDecrementRule.php new file mode 100644 index 0000000..d0e0809 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostDecrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPostDecrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PostDec::class; + } + + protected function describeOperation(): string + { + return 'post-decrement'; + } + + protected function getIdentifier(): string + { + return 'postDec'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostIncrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostIncrementRule.php new file mode 100644 index 0000000..400d828 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPostIncrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPostIncrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PostInc::class; + } + + protected function describeOperation(): string + { + return 'post-increment'; + } + + protected function getIdentifier(): string + { + return 'postInc'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreDecrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreDecrementRule.php new file mode 100644 index 0000000..9d58356 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreDecrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPreDecrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PreDec::class; + } + + protected function describeOperation(): string + { + return 'pre-decrement'; + } + + protected function getIdentifier(): string + { + return 'preDec'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreIncrementRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreIncrementRule.php new file mode 100644 index 0000000..d5d81f2 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandInArithmeticPreIncrementRule.php @@ -0,0 +1,28 @@ + + */ +class OperandInArithmeticPreIncrementRule extends OperandInArithmeticIncrementOrDecrementRule +{ + + public function getNodeType(): string + { + return PreInc::class; + } + + protected function describeOperation(): string + { + return 'pre-increment'; + } + + protected function getIdentifier(): string + { + return 'preInc'; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticAdditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticAdditionRule.php new file mode 100644 index 0000000..80de146 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticAdditionRule.php @@ -0,0 +1,69 @@ + + */ +class OperandsInArithmeticAdditionRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpPlus) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpPlus) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $leftType = $scope->getType($left); + $rightType = $scope->getType($right); + if (count($leftType->getArrays()) > 0 && count($rightType->getArrays()) > 0) { + return []; + } + + $messages = []; + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in +, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('plus.leftNonNumeric')->build(); + } + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in +, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('plus.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticDivisionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticDivisionRule.php new file mode 100644 index 0000000..e95b3d6 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticDivisionRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticDivisionRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpDiv) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpDiv) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in /, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('div.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in /, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('div.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticExponentiationRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticExponentiationRule.php new file mode 100644 index 0000000..1992b84 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticExponentiationRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticExponentiationRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpPow) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpPow) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in **, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('pow.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in **, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('pow.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticModuloRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticModuloRule.php new file mode 100644 index 0000000..5b5f3c3 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticModuloRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticModuloRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpMod) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpMod) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in %%, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mod.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in %%, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mod.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php new file mode 100644 index 0000000..353df4c --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticMultiplicationRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpMul) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpMul) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in *, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mul.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in *, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('mul.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticSubtractionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticSubtractionRule.php new file mode 100644 index 0000000..5559d60 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperandsInArithmeticSubtractionRule.php @@ -0,0 +1,65 @@ + + */ +class OperandsInArithmeticSubtractionRule implements Rule +{ + + private OperatorRuleHelper $helper; + + public function __construct(OperatorRuleHelper $helper) + { + $this->helper = $helper; + } + + public function getNodeType(): string + { + return Expr::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof BinaryOpMinus) { + $left = $node->left; + $right = $node->right; + } elseif ($node instanceof AssignOpMinus) { + $left = $node->var; + $right = $node->expr; + } else { + return []; + } + + $messages = []; + $leftType = $scope->getType($left); + if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in -, %s given on the left side.', + $leftType->describe(VerbosityLevel::typeOnly()), + ))->identifier('minus.leftNonNumeric')->build(); + } + + $rightType = $scope->getType($right); + if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { + $messages[] = RuleErrorBuilder::message(sprintf( + 'Only numeric types are allowed in -, %s given on the right side.', + $rightType->describe(VerbosityLevel::typeOnly()), + ))->identifier('minus.rightNonNumeric')->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperatorRuleHelper.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperatorRuleHelper.php new file mode 100644 index 0000000..6de54ca --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/Operators/OperatorRuleHelper.php @@ -0,0 +1,92 @@ +ruleLevelHelper = $ruleLevelHelper; + } + + public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return true; + } + + // already reported by PHPStan core + if ($type->toNumber() instanceof ErrorType) { + return true; + } + + return $this->isSubtypeOfNumber($scope, $expr); + } + + public function isValidForIncrement(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return true; + } + + if ($type->isString()->yes()) { + // Because `$a = 'a'; $a++;` is valid + return true; + } + + return $this->isSubtypeOfNumber($scope, $expr); + } + + public function isValidForDecrement(Scope $scope, Expr $expr): bool + { + $type = $scope->getType($expr); + if ($type instanceof MixedType) { + return true; + } + + return $this->isSubtypeOfNumber($scope, $expr); + } + + private function isSubtypeOfNumber(Scope $scope, Expr $expr): bool + { + $acceptedType = new UnionType([new IntegerType(), new FloatType(), new IntersectionType([new StringType(), new AccessoryNumericStringType()])]); + + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $expr, + '', + static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(), + )->getType(); + + if ($type instanceof ErrorType) { + return true; + } + + $isSuperType = $acceptedType->isSuperTypeOf($type); + if ($type instanceof BenevolentUnionType) { + return !$isSuperType->no(); + } + + return $isSuperType->yes(); + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsCallableRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsCallableRule.php new file mode 100644 index 0000000..492aa60 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsCallableRule.php @@ -0,0 +1,65 @@ + + */ +class DynamicCallOnStaticMethodsCallableRule implements Rule +{ + + private RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return MethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->getName() instanceof Node\Identifier) { + return []; + } + + $name = $node->getName()->name; + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->getVar(), + '', + static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(), + )->getType(); + + if ($type instanceof ErrorType || !$type->canCallMethods()->yes() || !$type->hasMethod($name)->yes()) { + return []; + } + + $methodReflection = $type->getMethod($name, $scope); + if ($methodReflection->isStatic()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Dynamic call to static method %s::%s().', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + ))->identifier('staticMethod.dynamicCall')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php new file mode 100644 index 0000000..c0ae18b --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php @@ -0,0 +1,76 @@ + + */ +class DynamicCallOnStaticMethodsRule implements Rule +{ + + private RuleLevelHelper $ruleLevelHelper; + + public function __construct(RuleLevelHelper $ruleLevelHelper) + { + $this->ruleLevelHelper = $ruleLevelHelper; + } + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Identifier) { + return []; + } + + $name = $node->name->name; + $type = $this->ruleLevelHelper->findTypeToCheck( + $scope, + $node->var, + '', + static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(), + )->getType(); + + if ($type instanceof ErrorType || !$type->canCallMethods()->yes() || !$type->hasMethod($name)->yes()) { + return []; + } + + $methodReflection = $type->getMethod($name, $scope); + if ($methodReflection->isStatic()) { + $prototype = $methodReflection->getPrototype(); + if (in_array($prototype->getDeclaringClass()->getName(), [ + TypeInferenceTestCase::class, + PHPStanTestCase::class, + ], true)) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Dynamic call to static method %s::%s().', + $methodReflection->getDeclaringClass()->getDisplayName(), + $methodReflection->getName(), + ))->identifier('staticMethod.dynamicCall')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/StrictFunctionCallsRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/StrictFunctionCallsRule.php new file mode 100644 index 0000000..f959fc9 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/StrictCalls/StrictFunctionCallsRule.php @@ -0,0 +1,96 @@ + + */ +class StrictFunctionCallsRule implements Rule +{ + + /** @var int[] */ + private array $functionArguments = [ + 'in_array' => 2, + 'array_search' => 2, + 'base64_decode' => 1, + 'array_keys' => 2, + ]; + + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + + public function getNodeType(): string + { + return FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Name) { + return []; + } + + if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { + return []; + } + + $function = $this->reflectionProvider->getFunction($node->name, $scope); + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $node->getArgs(), $function->getVariants()); + $node = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); + if ($node === null) { + return []; + } + $functionName = strtolower($function->getName()); + if (!array_key_exists($functionName, $this->functionArguments)) { + return []; + } + + if ($functionName === 'array_keys' && !array_key_exists(1, $node->getArgs())) { + return []; + } + + $argumentPosition = $this->functionArguments[$functionName]; + if (!array_key_exists($argumentPosition, $node->getArgs())) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to function %s() requires parameter #%d to be set.', + $functionName, + $argumentPosition + 1, + ))->identifier('function.strict')->build(), + ]; + } + + $argumentType = $scope->getType($node->getArgs()[$argumentPosition]->value); + $trueType = new ConstantBooleanType(true); + if (!$trueType->isSuperTypeOf($argumentType)->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Call to function %s() requires parameter #%d to be true.', + $functionName, + $argumentPosition + 1, + ))->identifier('function.strict')->build(), + ]; + } + + return []; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/SwitchConditions/MatchingTypeInSwitchCaseConditionRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/SwitchConditions/MatchingTypeInSwitchCaseConditionRule.php new file mode 100644 index 0000000..ba7c92f --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/SwitchConditions/MatchingTypeInSwitchCaseConditionRule.php @@ -0,0 +1,60 @@ + + */ +class MatchingTypeInSwitchCaseConditionRule implements Rule +{ + + private Printer $printer; + + public function __construct(Printer $printer) + { + $this->printer = $printer; + } + + public function getNodeType(): string + { + return Switch_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + $conditionType = $scope->getType($node->cond); + foreach ($node->cases as $case) { + if ($case->cond === null) { + continue; + } + + $caseType = $scope->getType($case->cond); + if (!$conditionType->isSuperTypeOf($caseType)->no()) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf( + 'Switch condition type (%s) does not match case condition %s (%s).', + $conditionType->describe(VerbosityLevel::value()), + $this->printer->prettyPrintExpr($case->cond), + $caseType->describe(VerbosityLevel::typeOnly()), + )) + ->line($case->getStartLine()) + ->identifier('switch.type') + ->build(); + } + + return $messages; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallRule.php new file mode 100644 index 0000000..d55fc78 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallRule.php @@ -0,0 +1,38 @@ + + */ +class VariableMethodCallRule implements Rule +{ + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable method call on %s.', + $scope->getType($node->var)->describe(VerbosityLevel::typeOnly()), + ))->identifier('method.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallableRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallableRule.php new file mode 100644 index 0000000..dd891a9 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableMethodCallableRule.php @@ -0,0 +1,38 @@ + + */ +class VariableMethodCallableRule implements Rule +{ + + public function getNodeType(): string + { + return MethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->getName() instanceof Node\Identifier) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable method call on %s.', + $scope->getType($node->getVar())->describe(VerbosityLevel::typeOnly()), + ))->identifier('method.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariablePropertyFetchRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariablePropertyFetchRule.php new file mode 100644 index 0000000..0d077b2 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariablePropertyFetchRule.php @@ -0,0 +1,98 @@ + + */ +class VariablePropertyFetchRule implements Rule +{ + + private ReflectionProvider $reflectionProvider; + + /** @var string[] */ + private array $universalObjectCratesClasses; + + /** + * @param string[] $universalObjectCratesClasses + */ + public function __construct(ReflectionProvider $reflectionProvider, array $universalObjectCratesClasses) + { + $this->reflectionProvider = $reflectionProvider; + $this->universalObjectCratesClasses = $universalObjectCratesClasses; + } + + public function getNodeType(): string + { + return PropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + $fetchedOnType = $scope->getType($node->var); + foreach ($fetchedOnType->getObjectClassNames() as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $classReflection = $this->reflectionProvider->getClass($referencedClass); + if ( + $this->isUniversalObjectCrate($classReflection) + || $this->isSimpleXMLElement($classReflection) + ) { + return []; + } + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable property access on %s.', + $fetchedOnType->describe(VerbosityLevel::typeOnly()), + ))->identifier('property.dynamicName')->build(), + ]; + } + + private function isSimpleXMLElement( + ClassReflection $classReflection + ): bool + { + return $classReflection->getName() === SimpleXMLElement::class + || $classReflection->isSubclassOf(SimpleXMLElement::class); + } + + private function isUniversalObjectCrate( + ClassReflection $classReflection + ): bool + { + foreach ($this->universalObjectCratesClasses as $className) { + if (!$this->reflectionProvider->hasClass($className)) { + continue; + } + + if ( + $classReflection->getName() === $className + || $classReflection->isSubclassOf($className) + ) { + return true; + } + } + + return false; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallRule.php new file mode 100644 index 0000000..963f01d --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallRule.php @@ -0,0 +1,44 @@ + + */ +class VariableStaticMethodCallRule implements Rule +{ + + public function getNodeType(): string + { + return StaticCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + if ($node->class instanceof Node\Name) { + $methodCalledOn = $scope->resolveName($node->class); + } else { + $methodCalledOn = $scope->getType($node->class)->describe(VerbosityLevel::typeOnly()); + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable static method call on %s.', + $methodCalledOn, + ))->identifier('staticMethod.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallableRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallableRule.php new file mode 100644 index 0000000..2cfebac --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticMethodCallableRule.php @@ -0,0 +1,44 @@ + + */ +class VariableStaticMethodCallableRule implements Rule +{ + + public function getNodeType(): string + { + return StaticMethodCallableNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->getName() instanceof Node\Identifier) { + return []; + } + + if ($node->getClass() instanceof Node\Name) { + $methodCalledOn = $scope->resolveName($node->getClass()); + } else { + $methodCalledOn = $scope->getType($node->getClass())->describe(VerbosityLevel::typeOnly()); + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable static method call on %s.', + $methodCalledOn, + ))->identifier('staticMethod.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticPropertyFetchRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticPropertyFetchRule.php new file mode 100644 index 0000000..bc47599 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableStaticPropertyFetchRule.php @@ -0,0 +1,44 @@ + + */ +class VariableStaticPropertyFetchRule implements Rule +{ + + public function getNodeType(): string + { + return StaticPropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->name instanceof Node\Identifier) { + return []; + } + + if ($node->class instanceof Node\Name) { + $propertyAccessedOn = $scope->resolveName($node->class); + } else { + $propertyAccessedOn = $scope->getType($node->class)->describe(VerbosityLevel::typeOnly()); + } + + return [ + RuleErrorBuilder::message(sprintf( + 'Variable static property access on %s.', + $propertyAccessedOn, + ))->identifier('staticProperty.dynamicName')->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableVariablesRule.php b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableVariablesRule.php new file mode 100644 index 0000000..f78e4ef --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan-strict-rules/src/Rules/VariableVariables/VariableVariablesRule.php @@ -0,0 +1,36 @@ + + */ +class VariableVariablesRule implements Rule +{ + + public function getNodeType(): string + { + return Variable::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (is_string($node->name)) { + return []; + } + + return [ + RuleErrorBuilder::message('Variable variables are not allowed.') + ->identifier('variable.dynamicName') + ->build(), + ]; + } + +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan/LICENSE b/tools/.phpstan/vendor/phpstan/phpstan/LICENSE new file mode 100644 index 0000000..e5f34e6 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2016 Ondřej Mirtes +Copyright (c) 2025 PHPStan s.r.o. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/.phpstan/vendor/phpstan/phpstan/README.md b/tools/.phpstan/vendor/phpstan/phpstan/README.md new file mode 100644 index 0000000..abae67e --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/README.md @@ -0,0 +1,108 @@ +

PHPStan - PHP Static Analysis Tool

+ +

+ PHPStan +

+ +

+ Build Status + Latest Stable Version + Total Downloads + License + PHPStan Enabled +

+ +------ + +PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs +even before you write tests for the code. It moves PHP closer to compiled languages in the sense that the correctness of each line of the code +can be checked before you run the actual line. + +**[Read more about PHPStan »](https://phpstan.org/)** + +**[Try out PHPStan on the on-line playground! »](https://phpstan.org/try)** + +## Sponsors + +TheCodingMachine +    +Private Packagist +
+CDN77 +    +Blackfire.io +
+iO +    +Fame Helsinki +
+ShipMonk +    +Togetter +
+RightCapital +    +ContentKing +
+ZOL +    +EdgeNext +
+Shopware +    +Craft CMS +
+Worksome +    +campoint AG +
+Crisp.nl +    +Inviqa +
+Shoptet +    +Route4Me: Route Optimizer and Route Planner Software +
+Belsimpel +    +TicketSwap + + +[**You can now sponsor my open-source work on PHPStan through GitHub Sponsors.**](https://github.com/sponsors/ondrejmirtes) + +Does GitHub already have your 💳? Do you use PHPStan to find 🐛 before they reach production? [Send a couple of 💸 a month my way too.](https://github.com/sponsors/ondrejmirtes) Thank you! + +One-time donations [through Revolut.me](https://revolut.me/ondrejmirtes) are also accepted. To request an invoice, [contact me](mailto:ondrej@mirtes.cz) through e-mail. + +## Documentation + +All the documentation lives on the [phpstan.org website](https://phpstan.org/): + +* [Getting Started & User Guide](https://phpstan.org/user-guide/getting-started) +* [Config Reference](https://phpstan.org/config-reference) +* [PHPDocs Basics](https://phpstan.org/writing-php-code/phpdocs-basics) & [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types) +* [Extension Library](https://phpstan.org/user-guide/extension-library) +* [Developing Extensions](https://phpstan.org/developing-extensions/extension-types) +* [API Reference](https://apiref.phpstan.org/) + +## PHPStan Pro + +PHPStan Pro is a paid add-on on top of open-source PHPStan Static Analysis Tool with these premium features: + +* Web UI for browsing found errors, you can click and open your editor of choice on the offending line. +* Continuous analysis (watch mode): scans changed files in the background, refreshes the UI automatically. + +Try it on PHPStan 0.12.45 or later by running it with the `--pro` option. You can create an account either by following the on-screen instructions, or by visiting [account.phpstan.com](https://account.phpstan.com/). + +After 30-day free trial period it costs 7 EUR for individuals monthly, 70 EUR for teams (up to 25 members). By paying for PHPStan Pro, you're supporting the development of open-source PHPStan. + +You can read more about it on [PHPStan's website](https://phpstan.org/blog/introducing-phpstan-pro). + +## Code of Conduct + +This project adheres to a [Contributor Code of Conduct](https://github.com/phpstan/phpstan/blob/master/CODE_OF_CONDUCT.md). By participating in this project and its community, you are expected to uphold this code. + +## Contributing + +Any contributions are welcome. PHPStan's source code open to pull requests lives at [`phpstan/phpstan-src`](https://github.com/phpstan/phpstan-src). diff --git a/tools/.phpstan/vendor/phpstan/phpstan/UPGRADING.md b/tools/.phpstan/vendor/phpstan/phpstan/UPGRADING.md new file mode 100644 index 0000000..5229490 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/UPGRADING.md @@ -0,0 +1,338 @@ +Upgrading from PHPStan 1.x to 2.0 +================================= + +## PHP version requirements + +PHPStan now requires PHP 7.4 or newer to run. + +## Upgrading guide for end users + +The best way to get ready for upgrade to PHPStan 2.0 is to update to the **latest PHPStan 1.12 release** +and enable [**Bleeding Edge**](https://phpstan.org/blog/what-is-bleeding-edge). This will enable the new rules and behaviours that 2.0 turns on for all users. + +Also make sure to install and enable [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules). + +Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in `composer.json`: + +```json +"require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-nette": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + ... +} +``` + +Don't forget to update [3rd party PHPStan extensions](https://phpstan.org/user-guide/extension-library) as well. + +After changing your `composer.json`, run `composer update 'phpstan/*' -W`. + +It's up to you whether you go through the new reported errors or if you just put them all to the [baseline](https://phpstan.org/user-guide/baseline) ;) Everyone who's on PHPStan 1.12 should be able to upgrade to PHPStan 2.0. + +### Noteworthy changes to code analysis + +* [**Enhancements in handling parameters passed by reference**](https://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference) +* [**Validate inline PHPDoc `@var` tag type**](https://phpstan.org/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type) +* [**List type enforced**](https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type) +* **Always `true` conditions always reported**: previously reported only with phpstan-strict-rules, this is now always reported. + +### Removed option `checkMissingIterableValueType` + +It's strongly recommended to add the missing array typehints. + +If you want to continue ignoring missing typehints from arrays, add `missingType.iterableValue` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.iterableValue +``` + +### Removed option `checkGenericClassInNonGenericObjectType` + +It's strongly recommended to add the missing generic typehints. + +If you want to continue ignoring missing typehints from generics, add `missingType.generics` error identifier to your `ignoreErrors`: + +```neon +parameters: + ignoreErrors: + - + identifier: missingType.generics +``` + +### Removed `checkAlwaysTrue*` options + +These options have been removed because PHPStan now always behaves as if these were set to `true`: + +* `checkAlwaysTrueCheckTypeFunctionCall` +* `checkAlwaysTrueInstanceof` +* `checkAlwaysTrueStrictComparison` +* `checkAlwaysTrueLooseComparison` + +### Removed option `excludes_analyse` + +It has been replaced with [`excludePaths`](https://phpstan.org/user-guide/ignoring-errors#excluding-whole-files). + +### Paths in `excludePaths` and `ignoreErrors` have to be a valid file path or a fnmatch pattern + +If you are excluding a file path that might not exist but you still want to have it in `excludePaths`, append `(?)`: + +```neon +parameters: + excludePaths: + - tests/*/data/* + - src/broken + - node_modules (?) # optional path, might not exist +``` + +If you have the same situation in `ignoreErrors` (ignoring an error in a path that might not exist), use `reportUnmatchedIgnoredErrors: false`. + +```neon +parameters: + reportUnmatchedIgnoredErrors: false +``` + +Appending `(?)` in `ignoreErrors` is not supported. + +### Changes in 1st party PHPStan extensions + +* [phpstan-doctrine](https://github.com/phpstan/phpstan-doctrine) + * Removed config parameter `searchOtherMethodsForQueryBuilderBeginning` (extension now behaves as when this was set to `true`) + * Removed config parameter `queryBuilderFastAlgorithm` (extension now behaves as when this was set to `false`) +* [phpstan-symfony](https://github.com/phpstan/phpstan-symfony) + * Removed legacy options with `_` in the name + * `container_xml_path` -> use `containerXmlPath` + * `constant_hassers` -> use `constantHassers` + * `console_application_loader` -> use `consoleApplicationLoader` + +### Minor backward compatibility breaks + +* Removed unused config parameter `cache.nodesByFileCountMax` +* Removed unused config parameter `memoryLimitFile` +* Removed unused feature toggle `disableRuntimeReflectionProvider` +* Removed unused config parameter `staticReflectionClassNamePatterns` +* Remove `fixerTmpDir` config parameter, use `pro.tmpDir` instead +* Remove `tempResultCachePath` config parameter, use `resultCachePath` instead +* `additionalConfigFiles` config parameter must be a list + +## Upgrading guide for extension developers + +> [!NOTE] +> Please switch to PHPStan 2.0 in a new major version of your extension. It's not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code. +> +> You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan & phpstan-deprecation-rules & Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring `"phpstan/phpstan": "^2.0"` in your `composer.json`, and releasing a new major version. + +### PHPStan now uses nikic/php-parser v5 + +See [UPGRADING](https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md) guide for PHP-Parser. + +The most notable change is how `throw` statement is represented. Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class, while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class. + +Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The +`Stmt\Throw_` class has been removed. + +### PHPStan now uses phpstan/phpdoc-parser v2 + +See [UPGRADING](https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md) guide for phpstan/phpdoc-parser. + +### Returning plain strings as errors no longer supported, use RuleErrorBuilder + +Identifiers are also required in custom rules. + +Learn more: [Using RuleErrorBuilder to enrich reported errors in custom rules](https://phpstan.org/blog/using-rule-error-builder) + +**Before**: + +```php +return ['My error']; +``` + +**After**: + +```php +return [ + RuleErrorBuilder::message('My error') + ->identifier('my.error') + ->build(), +]; +``` + +### Deprecate various `instanceof *Type` in favour of new methods on `Type` interface + +Learn more: [Why Is instanceof *Type Wrong and Getting Deprecated?](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) + +### Removed deprecated `ParametersAcceptorSelector::selectSingle()` + +Use [`ParametersAcceptorSelector::selectFromArgs()`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs) instead. It should be used in most places where `selectSingle()` was previously used, like dynamic return type extensions. + +**Before**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); +``` + +**After**: + +```php +$defaultReturnType = ParametersAcceptorSelector::selectFromArgs( + $scope, + $functionCall->getArgs(), + $functionReflection->getVariants() +)->getReturnType(); +``` + +If you're analysing function or method body itself and you're using one of the following methods, ask for `getParameters()` and `getReturnType()` directly on the reflection object: + +* [InClassMethodNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html) +* [InFunctionNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html) +* [FunctionReturnStatementsNode::getFunctionReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html) +* [MethodReturnStatementsNode::getMethodReflection()](https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html) +* [Scope::getFunction()](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction) + +**Before**: + +```php +$function = $node->getFunctionReflection(); +$returnType = ParametersAcceptorSelector::selectSingle($function->getVariants())->getReturnType(); +``` + +**After**: + +```php +$returnType = $node->getFunctionReflection()->getReturnType(); +``` + +### Changed `TypeSpecifier::create()` and `SpecifiedTypes` constructor parameters + +[`PHPStan\Analyser\TypeSpecifier::create()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create) now accepts (all parameters are required): + +* `Expr $expr` +* `Type $type` +* `TypeSpecifierContext $context` +* `Scope $scope` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by this method), call `setAlwaysOverwriteTypes()` and `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::create()`). These methods return a new object (SpecifiedTypes is immutable). + +[`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) constructor now accepts: + +* `array $sureTypes` +* `array $sureNotTypes` + +If you want to change `$overwrite` or `$rootExpr` (previous parameters also used to be accepted by the constructor), call `setAlwaysOverwriteTypes()` and `setRootExpr()`. These methods return a new object (SpecifiedTypes is immutable). + +### `ConstantArrayType` no longer extends `ArrayType` + +`Type::getArrays()` now returns `list`. + +Using `$type instanceof ArrayType` is [being deprecated anyway](https://phpstan.org/blog/why-is-instanceof-type-wrong-and-getting-deprecated) so the impact of this change should be minimal. + +### Changed `TypeSpecifier::specifyTypesInCondition()` + +This method no longer accepts `Expr $rootExpr`. If you want to change it, call `setRootExpr()` on [`SpecifiedTypes`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html) (object returned by `TypeSpecifier::specifyTypesInCondition()`). `setRootExpr()` method returns a new object (SpecifiedTypes is immutable). + +### Node attributes `parent`, `previous`, `next` are no longer available + +Learn more: https://phpstan.org/blog/preprocessing-ast-for-custom-rules + +### Removed config parameter `scopeClass` + +As a replacement you can implement [`PHPStan\Type\ExpressionTypeResolverExtension`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html) interface instead and register it as a service. + +### Removed `PHPStan\Broker\Broker` + +Use [`PHPStan\Reflection\ReflectionProvider`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ReflectionProvider.html) instead. + +`BrokerAwareExtension` was also removed. Ask for `ReflectionProvider` in the extension constructor instead. + +Instead of `PHPStanTestCase::createBroker()`, call `PHPStanTestCase::createReflectionProvider()`. + +### List type is enabled for everyone + +Removed static methods from `AccessoryArrayListType` class: + +* `isListTypeEnabled()` +* `setListTypeEnabled()` +* `intersectWith()` + +Instead of `AccessoryArrayListType::intersectWith($type)`, do `TypeCombinator::intersect($type, new AccessoryArrayListType())`. + +### Minor backward compatibility breaks + +* Classes that were previously `@final` were made `final` +* Parameter `$callableParameters` of [`MutatingScope::enterAnonymousFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction) and [`enterArrowFunction()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction) made required +* Parameter `StatementContext $context` of [`NodeScopeResolver::processStmtNodes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes) made required +* ClassPropertiesNode - remove `$extensions` parameter from [`getUninitializedProperties()`](https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties) +* `Type::getSmallerType()`, `Type::getSmallerOrEqualType()`, `Type::getGreaterType()`, `Type::getGreaterOrEqualType()`, `Type::isSmallerThan()`, `Type::isSmallerThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* `CompoundType::isGreaterThan()`, `CompoundType::isGreaterThanOrEqual()` now require [`PhpVersion`](https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html) as argument. +* Removed `ReflectionProvider::supportsAnonymousClasses()` (all reflection providers support anonymous classes) +* Remove `ArrayType::generalizeKeys()` +* Remove `ArrayType::count()`, use `Type::getArraySize()` instead +* Remove `ArrayType::castToArrayKeyType()`, `Type::toArrayKey()` instead +* Remove `UnionType::pickTypes()`, use `pickFromTypes()` instead +* Remove `RegexArrayShapeMatcher::matchType()`, use `matchExpr()` instead +* Remove unused `PHPStanTestCase::$useStaticReflectionProvider` +* Remove `PHPStanTestCase::getReflectors()`, use `getReflector()` instead +* Remove `ClassReflection::getFileNameWithPhpDocs()`, use `getFileName()` instead +* Remove `AnalysisResult::getInternalErrors()`, use `getInternalErrorObjects()` instead +* Remove `ConstantReflection::getValue()`, use `getValueExpr()` instead. To get `Type` from `Expr`, use `Scope::getType()` or `InitializerExprTypeResolver::getType()` +* Remove `PropertyTag::getType()`, use `getReadableType()` / `getWritableType()` instead +* Remove `GenericTypeVariableResolver`, use [`Type::getTemplateType()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType) instead +* Rename `Type::isClassStringType()` to `Type::isClassString()` +* Remove `Scope::isSpecified()`, use `hasExpressionType()` instead +* Remove `ConstantArrayType::isEmpty()`, use `isIterableAtLeastOnce()->no()` instead +* Remove `ConstantArrayType::getNextAutoIndex()` +* Removed methods from `ConstantArrayType` - `getFirst*Type` and `getLast*Type` + * Use `getFirstIterable*Type` and `getLastIterable*Type` instead +* Remove `ConstantArrayType::generalizeToArray()` +* Remove `ConstantArrayType::findTypeAndMethodName()`, use `findTypeAndMethodNames()` instead +* Remove `ConstantArrayType::removeLast()`, use [`Type::popArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray) instead +* Remove `ConstantArrayType::removeFirst()`, use [`Type::shiftArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray) instead +* Remove `ConstantArrayType::reverse()`, use [`Type::reverseArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray) instead +* Remove `ConstantArrayType::chunk()`, use [`Type::chunkArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray) instead +* Remove `ConstantArrayType::slice()`, use [`Type::sliceArray()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_sliceArray) instead +* Made `TypeUtils` thinner by removing methods: + * Remove `TypeUtils::getArrays()` and `getAnyArrays()`, use [`Type::getArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays) instead + * Remove `TypeUtils::getConstantArrays()` and `getOldConstantArrays()`, use [`Type::getConstantArrays()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays) instead + * Remove `TypeUtils::getConstantStrings()`, use [`Type::getConstantStrings()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings) instead + * Remove `TypeUtils::getConstantTypes()` and `getAnyConstantTypes()`, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) or [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) + * Remove `TypeUtils::generalizeType()`, use [`Type::generalize()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize) instead + * Remove `TypeUtils::getDirectClassNames()`, use [`Type::getObjectClassNames()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames) instead + * Remove `TypeUtils::getConstantScalars()`, use [`Type::isConstantScalarValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue) or [`Type::getConstantScalarTypes()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes) instead + * Remove `TypeUtils::getEnumCaseObjects()`, use [`Type::getEnumCases()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases) instead + * Remove `TypeUtils::containsCallable()`, use [`Type::isCallable()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable) instead +* Removed `Scope::doNotTreatPhpDocTypesAsCertain()`, use `getNativeType()` instead +* Parameter `$isList` in `ConstantArrayType` constructor can only be `TrinaryLogic`, no longer `bool` +* Parameter `$nextAutoIndexes` in `ConstantArrayType` constructor can only be `non-empty-list`, no longer `int` +* Remove `ConstantType` interface, use [`Type::isConstantValue()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue) instead +* `acceptsNamedArguments()` in `FunctionReflection`, `ExtendedMethodReflection` and `CallableParametersAcceptor` interfaces returns `TrinaryLogic` instead of `bool` +* Remove `FunctionReflection::isFinal()` +* [`Type::getProperty()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty) now returns [`ExtendedPropertyReflection`](https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html) +* Remove `__set_state()` on objects that should not be serialized in cache +* Parameter `$selfClass` of [`TypehintHelper::decideTypeFromReflection()`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection) no longer accepts `string` +* `LevelsTestCase::dataTopics()` data provider made static +* `PHPStan\Node\Printer\Printer` no longer autowired as `PhpParser\PrettyPrinter\Standard`, use `PHPStan\Node\Printer\Printer` in the typehint +* Remove `Type::acceptsWithReason()`, `Type:accepts()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Remove `CompoundType::isAcceptedWithReasonBy()`, `CompoundType::isAcceptedBy()` return type changed from `TrinaryLogic` to [`AcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +Remove `Type::isSuperTypeOfWithReason()`, `Type:isSuperTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `CompoundType::isSubTypeOfWithReasonBy()`, `CompoundType::isSubTypeOf()` return type changed from `TrinaryLogic` to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* Remove `TemplateType::isValidVarianceWithReason()`, changed `TemplateType::isValidVariance()` return type to [`IsSuperTypeOfResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html) +* `RuleLevelHelper::accepts()` return type changed from `bool` to [`RuleLevelHelperAcceptsResult`](https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html) +* Changes around `ClassConstantReflection` + * Class `ClassConstantReflection` removed from BC promise, renamed to `RealClassConstantReflection` + * Interface `ConstantReflection` renamed to `ClassConstantReflection` + * Added more methods around PHPDoc types and native types to the (new) `ClassConstantReflection` + * Interface `GlobalConstantReflection` renamed to `ConstantReflection` +* Renamed interfaces and classes from `*WithPhpDocs` to `Extended*` + * `ParametersAcceptorWithPhpDocs` -> `ExtendedParametersAcceptor` + * `ParameterReflectionWithPhpDocs` -> `ExtendedParameterReflection` + * `FunctionVariantWithPhpDocs` -> `ExtendedFunctionVariant` +* `ClassPropertyNode::getNativeType()` return type changed from AST node to `Type|null` +* Class `PHPStan\Node\ClassMethod` (accessible from `ClassMethodsNode`) is no longer an AST node + * Call `PHPStan\Node\ClassMethod::getNode()` to access the original AST node diff --git a/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php b/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php new file mode 100644 index 0000000..a5d341b --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/bootstrap.php @@ -0,0 +1,114 @@ +loadClass($class); + + return; + } + if (strpos($class, 'PHPStan\\') !== 0 || strpos($class, 'PHPStan\\PhpDocParser\\') === 0) { + return; + } + + if (!in_array('phar', stream_get_wrappers(), true)) { + throw new \Exception('Phar wrapper is not registered. Please review your php.ini settings.'); + } + + if (!self::$polyfillsLoaded) { + self::$polyfillsLoaded = true; + + if ( + PHP_VERSION_ID < 80000 + && empty($GLOBALS['__composer_autoload_files']['a4a119a56e50fbb293281d9a48007e0e']) + && !class_exists(\Symfony\Polyfill\Php80\Php80::class, false) + ) { + $GLOBALS['__composer_autoload_files']['a4a119a56e50fbb293281d9a48007e0e'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php80/Php80.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php80/bootstrap.php'; + } + + if ( + empty($GLOBALS['__composer_autoload_files']['0e6d7bf4a5811bfa5cf40c5ccd6fae6a']) + && !class_exists(\Symfony\Polyfill\Mbstring\Mbstring::class, false) + ) { + $GLOBALS['__composer_autoload_files']['0e6d7bf4a5811bfa5cf40c5ccd6fae6a'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-mbstring/Mbstring.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-mbstring/bootstrap.php'; + } + + if ( + empty($GLOBALS['__composer_autoload_files']['e69f7f6ee287b969198c3c9d6777bd38']) + && !class_exists(\Symfony\Polyfill\Intl\Normalizer\Normalizer::class, false) + ) { + $GLOBALS['__composer_autoload_files']['e69f7f6ee287b969198c3c9d6777bd38'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-normalizer/Normalizer.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-normalizer/bootstrap.php'; + } + + if ( + !extension_loaded('intl') + && empty($GLOBALS['__composer_autoload_files']['8825ede83f2f289127722d4e842cf7e8']) + && !class_exists(\Symfony\Polyfill\Intl\Grapheme\Grapheme::class, false) + ) { + $GLOBALS['__composer_autoload_files']['8825ede83f2f289127722d4e842cf7e8'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-grapheme/Grapheme.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-intl-grapheme/bootstrap.php'; + } + + if ( + PHP_VERSION_ID < 80100 + && empty ($GLOBALS['__composer_autoload_files']['23c18046f52bef3eea034657bafda50f']) + && !class_exists(\Symfony\Polyfill\Php81\Php81::class, false) + ) { + $GLOBALS['__composer_autoload_files']['23c18046f52bef3eea034657bafda50f'] = true; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/Php81.php'; + require_once 'phar://' . __DIR__ . '/phpstan.phar/vendor/symfony/polyfill-php81/bootstrap.php'; + } + } + + $filename = str_replace('\\', DIRECTORY_SEPARATOR, $class); + if (strpos($class, 'PHPStan\\BetterReflection\\') === 0) { + $filename = substr($filename, strlen('PHPStan\\BetterReflection\\')); + $filepath = 'phar://' . __DIR__ . '/phpstan.phar/vendor/ondrejmirtes/better-reflection/src/' . $filename . '.php'; + } else { + $filename = substr($filename, strlen('PHPStan\\')); + $filepath = 'phar://' . __DIR__ . '/phpstan.phar/src/' . $filename . '.php'; + } + + if (!file_exists($filepath)) { + return; + } + + require $filepath; + } +} + +spl_autoload_register([PharAutoloader::class, 'loadClass']); diff --git a/tools/.phpstan/vendor/phpstan/phpstan/composer.json b/tools/.phpstan/vendor/phpstan/phpstan/composer.json new file mode 100644 index 0000000..dc62c19 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/composer.json @@ -0,0 +1,26 @@ +{ + "name": "phpstan/phpstan", + "description": "PHPStan - PHP Static Analysis Tool", + "license": ["MIT"], + "keywords": ["dev", "static analysis"], + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "autoload": { + "files": ["bootstrap.php"] + }, + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "forum": "https://github.com/phpstan/phpstan/discussions", + "source": "https://github.com/phpstan/phpstan-src", + "docs": "https://phpstan.org/user-guide/getting-started", + "security": "https://github.com/phpstan/phpstan/security/policy" + } +} diff --git a/tools/.phpstan/vendor/phpstan/phpstan/conf/bleedingEdge.neon b/tools/.phpstan/vendor/phpstan/phpstan/conf/bleedingEdge.neon new file mode 100644 index 0000000..01fee97 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/conf/bleedingEdge.neon @@ -0,0 +1,2 @@ +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/tools/.phpstan/vendor/phpstan/phpstan/phpstan b/tools/.phpstan/vendor/phpstan/phpstan/phpstan new file mode 100755 index 0000000..7a08ef4 --- /dev/null +++ b/tools/.phpstan/vendor/phpstan/phpstan/phpstan @@ -0,0 +1,8 @@ +#!/usr/bin/env php + + +
+ AI abilities sea level rising... as way to rise type coverage for class elements +
+ +
+ +PHPStan uses type declarations to determine the type of variables, properties and other expression. Sometimes it's hard to see what PHPStan errors are the important ones among thousands of others. + +Instead of fixing all PHPStan errors at once, we can start with minimal require type coverage. + +
+ +What is the type coverage you ask? We have 4 type possible declarations in total here: + +```php +final class ConferenceFactory +{ + const SPEAKER_TAG = 'speaker'; + + private $talkFactory; + + public function createConference(array $data) + { + $talks = $this->talkFactory->create($data); + + return new Conference($talks); + } +} +``` + +*Note: Class constant types require PHP 8.3 to run.* + +The param type is defined. But the property, return and constant types are missing. + +* 1 out of 4 = 25 % coverage + +Our code quality is only at one-quarter of its potential. Let's get to 100 %! + +```diff + final class ConferenceFactory + { +- public const SPEAKER_TAG = 'speaker'; ++ public const string SPEAKER_TAG = 'speaker'; + +- private $talkFactory; ++ private TalkFactory $talkFactory; + +- public function createConference(array $data) ++ public function createConference(array $data): Conference + { + $talks = $this->talkFactory->create($data); + + return new Conference($talks); + } + } +``` + +This technique is very simple to start even on legacy project. Also, you're now aware exactly how high coverage your project has. + +
+ +## Install + +```bash +composer require tomasvotruba/type-coverage --dev +``` + +The package is available on PHP 7.2+ version in tagged releases. + +
+ +## Usage + +With [PHPStan extension installer](https://github.com/phpstan/extension-installer), everything is ready to run. + +Enable each item on their own: + +```yaml +# phpstan.neon +parameters: + type_coverage: + return: 50 + param: 35.5 + property: 70 + constant: 85 +``` + +
+ +## Measure Strict Declares coverage + +Once you've reached 100 % type coverage, make sure [your code is strict and uses types](https://tomasvotruba.com/blog/how-adding-type-declarations-makes-your-code-dangerous): + +```php + + +## Full Paths only + +If you run PHPStan only on some subpaths that are different from your setup in `phpstan.neon`, e.g.: + +```bash +vendor/bin/phpstan analyze src/Controller +``` + +This package could show false positives, as classes in the `src/Controller` could be slightly less typed. This would be spamming whole PHPStan output and make hard to see any other errors you look for. + +That's why this package only triggers if there are full paths, e.g.: + +```bash +vendor/bin/phpstan +```` + +
+ +Happy coding! diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json new file mode 100644 index 0000000..e32de7f --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/composer.json @@ -0,0 +1,24 @@ +{ + "name": "tomasvotruba/type-coverage", + "type": "phpstan-extension", + "description": "Measure type coverage of your project", + "license": "MIT", + "keywords": ["static analysis", "phpstan-extension"], + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0", + "nette/utils": "^3.2 || ^4.0" + }, + "autoload": { + "psr-4": { + "TomasVotruba\\TypeCoverage\\": "src" + } + }, + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/config/extension.neon b/tools/.phpstan/vendor/tomasvotruba/type-coverage/config/extension.neon new file mode 100644 index 0000000..b2a58b6 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/config/extension.neon @@ -0,0 +1,79 @@ +parametersSchema: + # see https://doc.nette.org/en/schema for configuration + type_coverage: structure([ + declare: anyOf(float(), int()) + # type declarations + return_type: anyOf(float(), int()) + param_type: anyOf(float(), int()) + property_type: anyOf(float(), int()) + constant_type: anyOf(float(), int()) + print_suggestions: bool() + # aliases to avoid typos + return: anyOf(schema(float(), nullable()), schema(int(), nullable())) + param: anyOf(schema(float(), nullable()), schema(int(), nullable())) + property: anyOf(schema(float(), nullable()), schema(int(), nullable())) + constant: anyOf(schema(float(), nullable()), schema(int(), nullable())) + + # measure + measure: bool() + ]) + +# default parameters +parameters: + type_coverage: + declare: 0 + # type declarations + return_type: 99 + param_type: 99 + property_type: 99 + constant_type: 99 + # default, yet deprecated + print_suggestions: true + # aliases + return: null + param: null + property: null + constant: null + + measure: false + +services: + - TomasVotruba\TypeCoverage\Formatter\TypeCoverageFormatter + - TomasVotruba\TypeCoverage\CollectorDataNormalizer + + - + factory: TomasVotruba\TypeCoverage\Configuration + arguments: + - %type_coverage% + + # collectors + - + class: TomasVotruba\TypeCoverage\Collectors\ReturnTypeDeclarationCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\TypeCoverage\Collectors\ParamTypeDeclarationCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\TypeCoverage\Collectors\PropertyTypeDeclarationCollector + tags: + - phpstan.collector + - + class: TomasVotruba\TypeCoverage\Collectors\ConstantTypeDeclarationCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\TypeCoverage\Collectors\DeclareCollector + tags: + - phpstan.collector + +rules: + - TomasVotruba\TypeCoverage\Rules\ParamTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\ReturnTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\PropertyTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\ConstantTypeCoverageRule + - TomasVotruba\TypeCoverage\Rules\DeclareCoverageRule diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/docs/required_type_level.jpg b/tools/.phpstan/vendor/tomasvotruba/type-coverage/docs/required_type_level.jpg new file mode 100644 index 0000000..cd219ab Binary files /dev/null and b/tools/.phpstan/vendor/tomasvotruba/type-coverage/docs/required_type_level.jpg differ diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/rector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/rector.php new file mode 100644 index 0000000..224fbf7 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/rector.php @@ -0,0 +1,18 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpSets() + ->withPreparedSets(deadCode: true, codeQuality: true, codingStyle: true, typeDeclarations: true, privatization: true, naming: true) + ->withImportNames(removeUnusedImports: true) + ->withSkip([ + '*/Fixture/*', + '*/Source/*', + ]); diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/CollectorDataNormalizer.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/CollectorDataNormalizer.php new file mode 100644 index 0000000..e05c09d --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/CollectorDataNormalizer.php @@ -0,0 +1,36 @@ +}>> $collectorDataByPath + */ + public function normalize(array $collectorDataByPath): TypeCountAndMissingTypes + { + $totalCount = 0; + $missingCount = 0; + + $missingTypeLinesByFilePath = []; + + foreach ($collectorDataByPath as $filePath => $typeCoverageData) { + foreach ($typeCoverageData as $nestedData) { + $totalCount += $nestedData[0]; + + $missingCount += count($nestedData[1]); + + $missingTypeLinesByFilePath[$filePath] = array_merge( + $missingTypeLinesByFilePath[$filePath] ?? [], + $nestedData[1] + ); + } + } + + return new TypeCountAndMissingTypes($totalCount, $missingCount, $missingTypeLinesByFilePath); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ConstantTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ConstantTypeDeclarationCollector.php new file mode 100644 index 0000000..4296b61 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ConstantTypeDeclarationCollector.php @@ -0,0 +1,77 @@ + + */ + public function getNodeType(): string + { + return ClassConstantsNode::class; + } + + /** + * @param ClassConstantsNode $node + * @return mixed[] + */ + public function processNode(Node $node, Scope $scope): array + { + // enable only on PHP 8.3+ + if (PHP_VERSION_ID < 80300) { + return [0, []]; + } + + $constantCount = count($node->getConstants()); + + $missingTypeLines = []; + + foreach ($node->getConstants() as $classConst) { + // blocked by parent type + if ($this->isGuardedByParentClassConstant($scope, $classConst)) { + continue; + } + + // already typed + if ($classConst->type instanceof Node) { + continue; + } + + // give useful context + $missingTypeLines[] = $classConst->getLine(); + } + + return [$constantCount, $missingTypeLines]; + } + + private function isGuardedByParentClassConstant(Scope $scope, ClassConst $classConst): bool + { + $constName = $classConst->consts[0]->name->toString(); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasConstant($constName)) { + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/DeclareCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/DeclareCollector.php new file mode 100644 index 0000000..ad677ee --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/DeclareCollector.php @@ -0,0 +1,51 @@ +getNodes() as $node) { + if (! $node instanceof Declare_) { + continue; + } + + foreach ($node->declares as $declare) { + if ( + $declare->key->name !== 'strict_types' + ) { + continue; + } + + if ( + ! $declare->value instanceof LNumber + || $declare->value->value !== 1 + ) { + return false; + } + + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ParamTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ParamTypeDeclarationCollector.php new file mode 100644 index 0000000..a9d4a49 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ParamTypeDeclarationCollector.php @@ -0,0 +1,72 @@ +shouldSkipFunctionLike($node)) { + return null; + } + + $missingTypeLines = []; + $paramCount = count($node->getParams()); + + foreach ($node->getParams() as $param) { + if ($param->variadic) { + // skip variadic + --$paramCount; + continue; + } + + if ($param->type === null) { + $missingTypeLines[] = $param->getLine(); + } + } + + return [$paramCount, $missingTypeLines]; + } + + private function shouldSkipFunctionLike(FunctionLike $functionLike): bool + { + // nothing to analyse + if ($functionLike->getParams() === []) { + return true; + } + + return $this->hasFunctionLikeCallableParam($functionLike); + } + + private function hasFunctionLikeCallableParam(FunctionLike $functionLike): bool + { + // skip callable, can be anythings + $docComment = $functionLike->getDocComment(); + if (! $docComment instanceof Doc) { + return false; + } + + $docCommentText = $docComment->getText(); + return strpos($docCommentText, '@param callable') !== false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/PropertyTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/PropertyTypeDeclarationCollector.php new file mode 100644 index 0000000..893a60b --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/PropertyTypeDeclarationCollector.php @@ -0,0 +1,93 @@ + + */ + public function getNodeType(): string + { + return InClassNode::class; + } + + /** + * @param InClassNode $node + * @return mixed[] + */ + public function processNode(Node $node, Scope $scope): array + { + // return typed properties/all properties + $classLike = $node->getOriginalNode(); + + $propertyCount = count($classLike->getProperties()); + + $missingTypeLines = []; + + foreach ($classLike->getProperties() as $property) { + // blocked by parent type + if ($this->isGuardedByParentClassProperty($scope, $property)) { + continue; + } + + // already typed + if ($property->type instanceof Node) { + continue; + } + + if ($this->isPropertyDocTyped($property)) { + continue; + } + + // give useful context + $missingTypeLines[] = $property->getLine(); + } + + return [$propertyCount, $missingTypeLines]; + } + + private function isPropertyDocTyped(Property $property): bool + { + $docComment = $property->getDocComment(); + if (! $docComment instanceof Doc) { + return false; + } + + $docCommentText = $docComment->getText(); + + // skip as unable to type + return strpos($docCommentText, 'callable') !== false || strpos($docCommentText, 'resource') !== false; + } + + private function isGuardedByParentClassProperty(Scope $scope, Property $property): bool + { + $propertyName = $property->props[0]->name->toString(); + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasProperty($propertyName)) { + return true; + } + } + + return false; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ReturnTypeDeclarationCollector.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ReturnTypeDeclarationCollector.php new file mode 100644 index 0000000..f6f63b8 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Collectors/ReturnTypeDeclarationCollector.php @@ -0,0 +1,48 @@ +isMagic()) { + return null; + } + + if ($scope->isInTrait()) { + $originalMethodName = $node->getAttribute('originalTraitMethodName'); + if ($originalMethodName === '__construct') { + return null; + } + } + + $missingTypeLines = []; + + if (! $node->returnType instanceof Node) { + $missingTypeLines[] = $node->getLine(); + } + + return [1, $missingTypeLines]; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration.php new file mode 100644 index 0000000..f2fd6f2 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration.php @@ -0,0 +1,76 @@ + + * @readonly + */ + private array $parameters; + + /** + * @param array $parameters + */ + public function __construct(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @return float|int + */ + public function getRequiredPropertyTypeLevel() + { + return $this->parameters['property'] ?? $this->parameters['property_type']; + } + + public function isConstantTypeCoverageEnabled(): bool + { + if (PHP_VERSION_ID < 80300) { + return false; + } + + return $this->getRequiredConstantTypeLevel() > 0; + } + + /** + * @return float|int + */ + public function getRequiredConstantTypeLevel() + { + return $this->parameters['constant'] ?? $this->parameters['constant_type']; + } + + /** + * @return float|int + */ + public function getRequiredParamTypeLevel() + { + return $this->parameters['param'] ?? $this->parameters['param_type']; + } + + /** + * @return float|int + */ + public function getRequiredReturnTypeLevel() + { + return $this->parameters['return'] ?? $this->parameters['return_type']; + } + + /** + * @return float|int + */ + public function getRequiredDeclareLevel() + { + return $this->parameters['declare']; + } + + public function showOnlyMeasure(): bool + { + return $this->parameters['measure']; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration/ScopeConfigurationResolver.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration/ScopeConfigurationResolver.php new file mode 100644 index 0000000..7b4cec2 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Configuration/ScopeConfigurationResolver.php @@ -0,0 +1,58 @@ +getParameter('analysedPaths'); + $analysedPathsFromConfig = $originalContainer->getParameter('analysedPathsFromConfig'); + + self::$areFullPathsAnalysed = $analysedPathsFromConfig === $analysedPaths; + + return self::$areFullPathsAnalysed; + } + + private static function getPrivateProperty(object $object, string $propertyName): object + { + $reflectionProperty = new ReflectionProperty($object, $propertyName); + $reflectionProperty->setAccessible(true); + + return $reflectionProperty->getValue($object); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Formatter/TypeCoverageFormatter.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Formatter/TypeCoverageFormatter.php new file mode 100644 index 0000000..04d7c39 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Formatter/TypeCoverageFormatter.php @@ -0,0 +1,56 @@ +getTotalCount() === 0) { + return []; + } + + $typeCoveragePercentage = $typeCountAndMissingTypes->getCoveragePercentage(); + + // has the code met the minimal sea level of types? + if ($typeCoveragePercentage >= $minimalLevel) { + return []; + } + + $ruleErrors = []; + + foreach ($typeCountAndMissingTypes->getMissingTypeLinesByFilePath() as $filePath => $lines) { + $errorMessage = sprintf( + $message, + $typeCountAndMissingTypes->getTotalCount(), + $typeCountAndMissingTypes->getFilledCount(), + $typeCoveragePercentage, + $minimalLevel + ); + + foreach ($lines as $line) { + $ruleErrors[] = RuleErrorBuilder::message($errorMessage) + ->identifier($identifier) + ->file($filePath) + ->line($line) + ->build(); + } + } + + return $ruleErrors; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ConstantTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ConstantTypeCoverageRule.php new file mode 100644 index 0000000..b53da09 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ConstantTypeCoverageRule.php @@ -0,0 +1,101 @@ + + */ +final class ConstantTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible constant types, only %d - %.1f %% actually have it. Add more constant types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.constantTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $constantTypeDeclarationCollector = $node->get(ConstantTypeDeclarationCollector::class); + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($constantTypeDeclarationCollector); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Class constant type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + if (! $this->configuration->isConstantTypeCoverageEnabled()) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredConstantTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/DeclareCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/DeclareCoverageRule.php new file mode 100644 index 0000000..314c0b5 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/DeclareCoverageRule.php @@ -0,0 +1,116 @@ + + */ +final class DeclareCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible declare(strict_types=1), only %d - %.1f %% actually have it. Add more declares to get over %s %%'; + + /** + * @readonly + */ + private Configuration $configuration; + + public function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $requiredDeclareLevel = $this->configuration->getRequiredDeclareLevel(); + + $declareCollector = $node->get(DeclareCollector::class); + $totalPossibleDeclares = count($declareCollector); + + $coveredDeclares = 0; + $notCoveredDeclareFilePaths = []; + + foreach ($declareCollector as $fileName => $data) { + // has declares + if ($data === [true]) { + ++$coveredDeclares; + } else { + $notCoveredDeclareFilePaths[] = $fileName; + } + } + + $declareCoverage = ($coveredDeclares / $totalPossibleDeclares) * 100; + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Strict declares coverage is %.1f %% out of %d possible', + $declareCoverage, + $totalPossibleDeclares + ); + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + // not enabled + if ($requiredDeclareLevel === 0) { + return []; + } + + // nothing to handle + if ($totalPossibleDeclares === 0) { + return []; + } + + // we meet the limit, all good + if ($declareCoverage >= $requiredDeclareLevel) { + return []; + } + + $ruleErrors = []; + foreach ($notCoveredDeclareFilePaths as $notCoveredDeclareFilePath) { + $errorMessage = sprintf( + self::ERROR_MESSAGE, + $totalPossibleDeclares, + $coveredDeclares, + $declareCoverage, + $requiredDeclareLevel, + ); + + $ruleErrors[] = RuleErrorBuilder::message($errorMessage)->file($notCoveredDeclareFilePath)->build(); + } + + return $ruleErrors; + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ParamTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ParamTypeCoverageRule.php new file mode 100644 index 0000000..ed300ee --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ParamTypeCoverageRule.php @@ -0,0 +1,105 @@ + + */ +final class ParamTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible param types, only %d - %.1f %% actually have it. Add more param types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.paramTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $paramTypeDeclarationCollector = $node->get(ParamTypeDeclarationCollector::class); + + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($paramTypeDeclarationCollector); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Param type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + + $ruleError = RuleErrorBuilder::message($errorMessage) + ->build(); + + return [$ruleError]; + } + + if ($this->configuration->getRequiredParamTypeLevel() === 0) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredParamTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/PropertyTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/PropertyTypeCoverageRule.php new file mode 100644 index 0000000..27dcec2 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/PropertyTypeCoverageRule.php @@ -0,0 +1,101 @@ + + */ +final class PropertyTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible property types, only %d - %.1f %% actually have it. Add more property types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.propertyTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $propertyTypeDeclarationCollector = $node->get(PropertyTypeDeclarationCollector::class); + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($propertyTypeDeclarationCollector); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Property type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + if ($this->configuration->getRequiredPropertyTypeLevel() === 0) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredPropertyTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ReturnTypeCoverageRule.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ReturnTypeCoverageRule.php new file mode 100644 index 0000000..c7e7fba --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/Rules/ReturnTypeCoverageRule.php @@ -0,0 +1,100 @@ + + */ +final class ReturnTypeCoverageRule implements Rule +{ + /** + * @var string + */ + public const ERROR_MESSAGE = 'Out of %d possible return types, only %d - %.1f %% actually have it. Add more return types to get over %s %%'; + + /** + * @var string + */ + private const IDENTIFIER = 'typeCoverage.returnTypeCoverage'; + + /** + * @readonly + */ + private TypeCoverageFormatter $typeCoverageFormatter; + + /** + * @readonly + */ + private Configuration $configuration; + + /** + * @readonly + */ + private CollectorDataNormalizer $collectorDataNormalizer; + + public function __construct(TypeCoverageFormatter $typeCoverageFormatter, Configuration $configuration, CollectorDataNormalizer $collectorDataNormalizer) + { + $this->typeCoverageFormatter = $typeCoverageFormatter; + $this->configuration = $configuration; + $this->collectorDataNormalizer = $collectorDataNormalizer; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + // if only subpaths are analysed, skip as data will be false positive + if (! ScopeConfigurationResolver::areFullPathsAnalysed($scope)) { + return []; + } + + $returnSeaLevelDataByFilePath = $node->get(ReturnTypeDeclarationCollector::class); + $typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($returnSeaLevelDataByFilePath); + + if ($this->configuration->showOnlyMeasure()) { + $errorMessage = sprintf( + 'Return type coverage is %.1f %% out of %d possible', + $typeCountAndMissingTypes->getCoveragePercentage(), + $typeCountAndMissingTypes->getTotalCount() + ); + return [RuleErrorBuilder::message($errorMessage)->build()]; + } + + if ($this->configuration->getRequiredReturnTypeLevel() === 0) { + return []; + } + + return $this->typeCoverageFormatter->formatErrors( + self::ERROR_MESSAGE, + self::IDENTIFIER, + $this->configuration->getRequiredReturnTypeLevel(), + $typeCountAndMissingTypes + ); + } +} diff --git a/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/ValueObject/TypeCountAndMissingTypes.php b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/ValueObject/TypeCountAndMissingTypes.php new file mode 100644 index 0000000..c539839 --- /dev/null +++ b/tools/.phpstan/vendor/tomasvotruba/type-coverage/src/ValueObject/TypeCountAndMissingTypes.php @@ -0,0 +1,75 @@ + + * @readonly + */ + private array $missingTypeLinesByFilePath; + + /** + * @param array $missingTypeLinesByFilePath + */ + public function __construct(int $totalCount, int $missingCount, array $missingTypeLinesByFilePath) + { + $this->totalCount = $totalCount; + $this->missingCount = $missingCount; + $this->missingTypeLinesByFilePath = $missingTypeLinesByFilePath; + } + + public function getTotalCount(): int + { + return $this->totalCount; + } + + public function getFilledCount(): int + { + return $this->totalCount - $this->missingCount; + } + + /** + * @return array + */ + public function getMissingTypeLinesByFilePath(): array + { + return $this->missingTypeLinesByFilePath; + } + + public function getCoveragePercentage(): float + { + if ($this->totalCount === 0) { + return 100.0; + } + + $relative = 100 * ($this->getTypedCount() / $this->totalCount); + + // round down with one decimal, to make error message clear that required value is not reached yet + return floor($relative * 10) / 10; + } + + private function getTypedCount(): int + { + $missingCount = 0; + + foreach ($this->missingTypeLinesByFilePath as $missingTypeLines) { + $missingCount += count($missingTypeLines); + } + + return $this->totalCount - $missingCount; + } +} diff --git a/tools/phive b/tools/phive new file mode 100755 index 0000000..27e468d --- /dev/null +++ b/tools/phive @@ -0,0 +1,1107 @@ +#!/usr/bin/env php + '/vendor/phar-io/executor/src/ExecutorException.php', + 'phario\\executor\\executor' => '/vendor/phar-io/executor/src/Executor.php', + 'phario\\executor\\executorresult' => '/vendor/phar-io/executor/src/ExecutorResult.php', + 'phario\\filesystem\\directory' => '/vendor/phar-io/filesystem/src/Directory.php', + 'phario\\filesystem\\directoryexception' => '/vendor/phar-io/filesystem/src/DirectoryException.php', + 'phario\\filesystem\\exception' => '/vendor/phar-io/filesystem/src/Exception.php', + 'phario\\filesystem\\file' => '/vendor/phar-io/filesystem/src/File.php', + 'phario\\filesystem\\filename' => '/vendor/phar-io/filesystem/src/Filename.php', + 'phario\\filesystem\\filenameexception' => '/vendor/phar-io/filesystem/src/FilenameException.php', + 'phario\\filesystem\\lastmodifieddate' => '/vendor/phar-io/filesystem/src/LastModifiedDate.php', + 'phario\\gnupg\\errorstrings' => '/vendor/phar-io/gnupg/src/ErrorStrings.php', + 'phario\\gnupg\\exception' => '/vendor/phar-io/gnupg/src/Exception.php', + 'phario\\gnupg\\factory' => '/vendor/phar-io/gnupg/src/Factory.php', + 'phario\\gnupg\\gnupg' => '/vendor/phar-io/gnupg/src/GnuPG.php', + 'phario\\manifest\\application' => '/vendor/phar-io/manifest/src/values/Application.php', + 'phario\\manifest\\applicationname' => '/vendor/phar-io/manifest/src/values/ApplicationName.php', + 'phario\\manifest\\author' => '/vendor/phar-io/manifest/src/values/Author.php', + 'phario\\manifest\\authorcollection' => '/vendor/phar-io/manifest/src/values/AuthorCollection.php', + 'phario\\manifest\\authorcollectioniterator' => '/vendor/phar-io/manifest/src/values/AuthorCollectionIterator.php', + 'phario\\manifest\\authorelement' => '/vendor/phar-io/manifest/src/xml/AuthorElement.php', + 'phario\\manifest\\authorelementcollection' => '/vendor/phar-io/manifest/src/xml/AuthorElementCollection.php', + 'phario\\manifest\\bundledcomponent' => '/vendor/phar-io/manifest/src/values/BundledComponent.php', + 'phario\\manifest\\bundledcomponentcollection' => '/vendor/phar-io/manifest/src/values/BundledComponentCollection.php', + 'phario\\manifest\\bundledcomponentcollectioniterator' => '/vendor/phar-io/manifest/src/values/BundledComponentCollectionIterator.php', + 'phario\\manifest\\bundleselement' => '/vendor/phar-io/manifest/src/xml/BundlesElement.php', + 'phario\\manifest\\componentelement' => '/vendor/phar-io/manifest/src/xml/ComponentElement.php', + 'phario\\manifest\\componentelementcollection' => '/vendor/phar-io/manifest/src/xml/ComponentElementCollection.php', + 'phario\\manifest\\containselement' => '/vendor/phar-io/manifest/src/xml/ContainsElement.php', + 'phario\\manifest\\copyrightelement' => '/vendor/phar-io/manifest/src/xml/CopyrightElement.php', + 'phario\\manifest\\copyrightinformation' => '/vendor/phar-io/manifest/src/values/CopyrightInformation.php', + 'phario\\manifest\\elementcollection' => '/vendor/phar-io/manifest/src/xml/ElementCollection.php', + 'phario\\manifest\\elementcollectionexception' => '/vendor/phar-io/manifest/src/exceptions/ElementCollectionException.php', + 'phario\\manifest\\email' => '/vendor/phar-io/manifest/src/values/Email.php', + 'phario\\manifest\\exception' => '/vendor/phar-io/manifest/src/exceptions/Exception.php', + 'phario\\manifest\\extelement' => '/vendor/phar-io/manifest/src/xml/ExtElement.php', + 'phario\\manifest\\extelementcollection' => '/vendor/phar-io/manifest/src/xml/ExtElementCollection.php', + 'phario\\manifest\\extension' => '/vendor/phar-io/manifest/src/values/Extension.php', + 'phario\\manifest\\extensionelement' => '/vendor/phar-io/manifest/src/xml/ExtensionElement.php', + 'phario\\manifest\\invalidapplicationnameexception' => '/vendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php', + 'phario\\manifest\\invalidemailexception' => '/vendor/phar-io/manifest/src/exceptions/InvalidEmailException.php', + 'phario\\manifest\\invalidurlexception' => '/vendor/phar-io/manifest/src/exceptions/InvalidUrlException.php', + 'phario\\manifest\\library' => '/vendor/phar-io/manifest/src/values/Library.php', + 'phario\\manifest\\license' => '/vendor/phar-io/manifest/src/values/License.php', + 'phario\\manifest\\licenseelement' => '/vendor/phar-io/manifest/src/xml/LicenseElement.php', + 'phario\\manifest\\manifest' => '/vendor/phar-io/manifest/src/values/Manifest.php', + 'phario\\manifest\\manifestdocument' => '/vendor/phar-io/manifest/src/xml/ManifestDocument.php', + 'phario\\manifest\\manifestdocumentexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestDocumentException.php', + 'phario\\manifest\\manifestdocumentloadingexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.php', + 'phario\\manifest\\manifestdocumentmapper' => '/vendor/phar-io/manifest/src/ManifestDocumentMapper.php', + 'phario\\manifest\\manifestdocumentmapperexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.php', + 'phario\\manifest\\manifestelement' => '/vendor/phar-io/manifest/src/xml/ManifestElement.php', + 'phario\\manifest\\manifestelementexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestElementException.php', + 'phario\\manifest\\manifestloader' => '/vendor/phar-io/manifest/src/ManifestLoader.php', + 'phario\\manifest\\manifestloaderexception' => '/vendor/phar-io/manifest/src/exceptions/ManifestLoaderException.php', + 'phario\\manifest\\manifestserializer' => '/vendor/phar-io/manifest/src/ManifestSerializer.php', + 'phario\\manifest\\noemailaddressexception' => '/vendor/phar-io/manifest/src/exceptions/NoEmailAddressException.php', + 'phario\\manifest\\phpelement' => '/vendor/phar-io/manifest/src/xml/PhpElement.php', + 'phario\\manifest\\phpextensionrequirement' => '/vendor/phar-io/manifest/src/values/PhpExtensionRequirement.php', + 'phario\\manifest\\phpversionrequirement' => '/vendor/phar-io/manifest/src/values/PhpVersionRequirement.php', + 'phario\\manifest\\requirement' => '/vendor/phar-io/manifest/src/values/Requirement.php', + 'phario\\manifest\\requirementcollection' => '/vendor/phar-io/manifest/src/values/RequirementCollection.php', + 'phario\\manifest\\requirementcollectioniterator' => '/vendor/phar-io/manifest/src/values/RequirementCollectionIterator.php', + 'phario\\manifest\\requireselement' => '/vendor/phar-io/manifest/src/xml/RequiresElement.php', + 'phario\\manifest\\type' => '/vendor/phar-io/manifest/src/values/Type.php', + 'phario\\manifest\\url' => '/vendor/phar-io/manifest/src/values/Url.php', + 'phario\\phive\\abstractrequestedpharresolver' => '/src/services/resolver/AbstractRequestedPharResolver.php', + 'phario\\phive\\abstractresolvingstrategy' => '/src/services/resolver/strategy/AbstractResolvingStrategy.php', + 'phario\\phive\\authconfig' => '/src/shared/config/AuthConfig.php', + 'phario\\phive\\authentication' => '/src/shared/http/Authentication.php', + 'phario\\phive\\authexception' => '/src/shared/exceptions/AuthException.php', + 'phario\\phive\\authxmlconfig' => '/src/shared/config/AuthXmlConfig.php', + 'phario\\phive\\authxmlconfigfilelocator' => '/src/shared/config/AuthXmlConfigFileLocator.php', + 'phario\\phive\\basehash' => '/src/shared/hash/BaseHash.php', + 'phario\\phive\\basicauthentication' => '/src/shared/http/authentication/BasicAuthentication.php', + 'phario\\phive\\bearerauthentication' => '/src/shared/http/authentication/BearerAuthentication.php', + 'phario\\phive\\cachebackend' => '/src/shared/http/CacheBackend.php', + 'phario\\phive\\checksumservice' => '/src/services/checksum/ChecksumService.php', + 'phario\\phive\\cli\\coloredconsoleoutput' => '/src/shared/cli/output/ColoredConsoleOutput.php', + 'phario\\phive\\cli\\command' => '/src/shared/cli/Command.php', + 'phario\\phive\\cli\\commandlocator' => '/src/shared/cli/CommandLocator.php', + 'phario\\phive\\cli\\commandlocatorexception' => '/src/shared/cli/CommandLocatorException.php', + 'phario\\phive\\cli\\commandoptionsexception' => '/src/shared/cli/CommandOptionsException.php', + 'phario\\phive\\cli\\consoleinput' => '/src/shared/cli/input/ConsoleInput.php', + 'phario\\phive\\cli\\consoleoutput' => '/src/shared/cli/output/ConsoleOutput.php', + 'phario\\phive\\cli\\consoletable' => '/src/shared/cli/output/ConsoleTable.php', + 'phario\\phive\\cli\\context' => '/src/shared/cli/Context.php', + 'phario\\phive\\cli\\contextexception' => '/src/shared/cli/ContextException.php', + 'phario\\phive\\cli\\generalcontext' => '/src/shared/cli/GeneralContext.php', + 'phario\\phive\\cli\\input' => '/src/shared/cli/input/Input.php', + 'phario\\phive\\cli\\options' => '/src/shared/cli/Options.php', + 'phario\\phive\\cli\\output' => '/src/shared/cli/output/Output.php', + 'phario\\phive\\cli\\outputfactory' => '/src/shared/cli/output/OutputFactory.php', + 'phario\\phive\\cli\\outputlocator' => '/src/shared/cli/output/OutputLocator.php', + 'phario\\phive\\cli\\request' => '/src/shared/cli/Request.php', + 'phario\\phive\\cli\\requestexception' => '/src/shared/cli/RequestException.php', + 'phario\\phive\\cli\\runner' => '/src/shared/cli/Runner.php', + 'phario\\phive\\cli\\runnerexception' => '/src/shared/cli/RunnerException.php', + 'phario\\phive\\commandlocator' => '/src/commands/CommandLocator.php', + 'phario\\phive\\compatibilityservice' => '/src/services/phar/CompatibilityService.php', + 'phario\\phive\\composeralias' => '/src/shared/ComposerAlias.php', + 'phario\\phive\\composercommand' => '/src/commands/composer/ComposerCommand.php', + 'phario\\phive\\composercommandconfig' => '/src/commands/composer/ComposerCommandConfig.php', + 'phario\\phive\\composercontext' => '/src/commands/composer/ComposerContext.php', + 'phario\\phive\\composerservice' => '/src/commands/composer/ComposerService.php', + 'phario\\phive\\compositeauthconfig' => '/src/shared/config/CompositeAuthConfig.php', + 'phario\\phive\\config' => '/src/shared/config/Config.php', + 'phario\\phive\\configexception' => '/src/shared/exceptions/ConfigException.php', + 'phario\\phive\\configuredphar' => '/src/shared/phar/ConfiguredPhar.php', + 'phario\\phive\\configuredpharexception' => '/src/shared/phar/ConfiguredPharException.php', + 'phario\\phive\\curl' => '/src/shared/http/Curl.php', + 'phario\\phive\\curlconfig' => '/src/shared/http/CurlConfig.php', + 'phario\\phive\\curlconfigbuilder' => '/src/shared/http/CurlConfigBuilder.php', + 'phario\\phive\\curlconfigexception' => '/src/shared/exceptions/CurlConfigException.php', + 'phario\\phive\\curlexception' => '/src/shared/exceptions/CurlException.php', + 'phario\\phive\\curlhttpclient' => '/src/shared/http/CurlHttpClient.php', + 'phario\\phive\\defaultcommand' => '/src/commands/default/DefaultCommand.php', + 'phario\\phive\\defaultcommandconfig' => '/src/commands/default/DefaultCommandConfig.php', + 'phario\\phive\\directurlresolver' => '/src/services/resolver/DirectUrlResolver.php', + 'phario\\phive\\downloadfailedexception' => '/src/shared/exceptions/DownloadFailedException.php', + 'phario\\phive\\environment' => '/src/shared/environment/Environment.php', + 'phario\\phive\\environmentauthconfig' => '/src/shared/config/EnvironmentAuthConfig.php', + 'phario\\phive\\environmentexception' => '/src/shared/exceptions/EnvironmentException.php', + 'phario\\phive\\environmentlocator' => '/src/shared/environment/EnvironmentLocator.php', + 'phario\\phive\\errorexception' => '/src/shared/exceptions/ErrorException.php', + 'phario\\phive\\etag' => '/src/shared/http/ETag.php', + 'phario\\phive\\exception' => '/src/shared/exceptions/Exception.php', + 'phario\\phive\\executor' => '/src/shared/executor/Executor.php', + 'phario\\phive\\executorexception' => '/src/shared/exceptions/ExecutorException.php', + 'phario\\phive\\executorresult' => '/src/shared/executor/ExecutorResult.php', + 'phario\\phive\\factory' => '/src/Factory.php', + 'phario\\phive\\featuremissingexception' => '/src/shared/exceptions/FeatureMissingException.php', + 'phario\\phive\\filedownloader' => '/src/shared/download/FileDownloader.php', + 'phario\\phive\\filedownloaderexception' => '/src/shared/FileDownloaderException.php', + 'phario\\phive\\filemigration' => '/src/services/migration/FileMigration.php', + 'phario\\phive\\filenotwritableexception' => '/src/shared/exceptions/FileNotWritableException.php', + 'phario\\phive\\filestoragecachebackend' => '/src/shared/http/FileStorageCacheBackend.php', + 'phario\\phive\\git' => '/src/shared/Git.php', + 'phario\\phive\\gitawarephiveversion' => '/src/shared/version/GitAwarePhiveVersion.php', + 'phario\\phive\\gitexception' => '/src/shared/exceptions/GitException.php', + 'phario\\phive\\githubaliasresolver' => '/src/services/resolver/GithubAliasResolver.php', + 'phario\\phive\\githubaliasresolverexception' => '/src/GithubAliasResolverException.php', + 'phario\\phive\\githubrepository' => '/src/shared/repository/GithubRepository.php', + 'phario\\phive\\gitlabaliasresolver' => '/src/services/resolver/GitlabAliasResolver.php', + 'phario\\phive\\gitlabrepository' => '/src/shared/repository/GitlabRepository.php', + 'phario\\phive\\globalphivexmlconfig' => '/src/shared/config/GlobalPhiveXmlConfig.php', + 'phario\\phive\\gnupg' => '/src/shared/GnuPG.php', + 'phario\\phive\\gnupgkeydownloader' => '/src/services/key/gpg/GnupgKeyDownloader.php', + 'phario\\phive\\gnupgkeydownloaderexception' => '/src/shared/exceptions/GnupgKeyDownloaderException.php', + 'phario\\phive\\gnupgkeyimporter' => '/src/services/key/gpg/GnupgKeyImporter.php', + 'phario\\phive\\gnupgsignatureverifier' => '/src/services/signature/gpg/GnupgSignatureVerifier.php', + 'phario\\phive\\gnupgverificationresult' => '/src/services/signature/gpg/GnupgVerificationResult.php', + 'phario\\phive\\hash' => '/src/shared/hash/Hash.php', + 'phario\\phive\\helpcommand' => '/src/commands/help/HelpCommand.php', + 'phario\\phive\\homepharsxmlmigration' => '/src/services/migration/HomePharsXmlMigration.php', + 'phario\\phive\\homephivexmlmigration' => '/src/services/migration/HomePhiveXmlMigration.php', + 'phario\\phive\\httpclient' => '/src/shared/http/HttpClient.php', + 'phario\\phive\\httpexception' => '/src/shared/http/HttpException.php', + 'phario\\phive\\httpprogresshandler' => '/src/shared/http/HttpProgressHandler.php', + 'phario\\phive\\httpprogressrenderer' => '/src/shared/http/HttpProgressRenderer.php', + 'phario\\phive\\httpprogressupdate' => '/src/shared/http/HttpProgressUpdate.php', + 'phario\\phive\\httpresponse' => '/src/shared/http/HttpResponse.php', + 'phario\\phive\\httpresponseexception' => '/src/shared/http/HttpResponseException.php', + 'phario\\phive\\installationfailedexception' => '/src/shared/exceptions/InstallationFailedException.php', + 'phario\\phive\\installcommand' => '/src/commands/install/InstallCommand.php', + 'phario\\phive\\installcommandconfig' => '/src/commands/install/InstallCommandConfig.php', + 'phario\\phive\\installcommandconfigexception' => '/src/commands/install/InstallCommandConfigException.php', + 'phario\\phive\\installcontext' => '/src/commands/install/InstallContext.php', + 'phario\\phive\\installedphar' => '/src/shared/phar/InstalledPhar.php', + 'phario\\phive\\installservice' => '/src/services/phar/InstallService.php', + 'phario\\phive\\internalfilemigration' => '/src/services/migration/InternalFileMigration.php', + 'phario\\phive\\invalidhashexception' => '/src/shared/exceptions/InvalidHashException.php', + 'phario\\phive\\invalidxmlexception' => '/src/shared/exceptions/InvalidXmlException.php', + 'phario\\phive\\ioexception' => '/src/shared/exceptions/IOException.php', + 'phario\\phive\\jsondata' => '/src/shared/JsonData.php', + 'phario\\phive\\keydownloader' => '/src/services/key/KeyDownloader.php', + 'phario\\phive\\keyimporter' => '/src/services/key/KeyImporter.php', + 'phario\\phive\\keyimportresult' => '/src/services/key/KeyImportResult.php', + 'phario\\phive\\keyservice' => '/src/services/key/KeyService.php', + 'phario\\phive\\linkcreationfailedexception' => '/src/shared/exceptions/LinkCreationFailedException.php', + 'phario\\phive\\listcommand' => '/src/commands/list/ListCommand.php', + 'phario\\phive\\localaliasresolver' => '/src/services/resolver/LocalAliasResolver.php', + 'phario\\phive\\localfirstresolvingstrategy' => '/src/services/resolver/strategy/LocalFirstResolvingStrategy.php', + 'phario\\phive\\localphivexmlconfig' => '/src/shared/config/LocalPhiveXmlConfig.php', + 'phario\\phive\\localrepository' => '/src/shared/repository/LocalRepository.php', + 'phario\\phive\\localsourceslistfileloader' => '/src/shared/sources/LocalSourcesListFileLoader.php', + 'phario\\phive\\localsslcertificate' => '/src/shared/http/LocalSslCertificate.php', + 'phario\\phive\\migratecommand' => '/src/commands/migrate/MigrateCommand.php', + 'phario\\phive\\migratecommandconfig' => '/src/commands/migrate/MigrateCommandConfig.php', + 'phario\\phive\\migratecontext' => '/src/commands/migrate/MigrateContext.php', + 'phario\\phive\\migration' => '/src/services/migration/Migration.php', + 'phario\\phive\\migrationexception' => '/src/shared/exceptions/MigrationException.php', + 'phario\\phive\\migrationfactory' => '/src/services/migration/MigrationFactory.php', + 'phario\\phive\\migrationservice' => '/src/services/migration/MigrationService.php', + 'phario\\phive\\migrationsfailedexception' => '/src/shared/exceptions/MigrationsFailedException.php', + 'phario\\phive\\nogpgbinaryfoundexception' => '/src/shared/exceptions/NoGPGBinaryFoundException.php', + 'phario\\phive\\notfoundexception' => '/src/shared/exceptions/NotFoundException.php', + 'phario\\phive\\outdatedcommand' => '/src/commands/outdated/OutdatedCommand.php', + 'phario\\phive\\outdatedconfig' => '/src/commands/outdated/OutdatedConfig.php', + 'phario\\phive\\outdatedconfigexception' => '/src/commands/outdated/OutdatedConfigException.php', + 'phario\\phive\\outdatedcontext' => '/src/commands/outdated/OutdatedContext.php', + 'phario\\phive\\phar' => '/src/shared/phar/Phar.php', + 'phario\\phive\\pharalias' => '/src/shared/phar/PharAlias.php', + 'phario\\phive\\phardownloader' => '/src/services/phar/PharDownloader.php', + 'phario\\phive\\pharexception' => '/src/shared/exceptions/PharException.php', + 'phario\\phive\\pharidentifier' => '/src/shared/phar/PharIdentifier.php', + 'phario\\phive\\pharinstaller' => '/src/services/phar/PharInstaller.php', + 'phario\\phive\\pharinstallerexception' => '/src/shared/exceptions/PharInstallerException.php', + 'phario\\phive\\pharinstallerfactory' => '/src/services/phar/PharInstallerFactory.php', + 'phario\\phive\\pharinstallerlocator' => '/src/services/phar/PharInstallerLocator.php', + 'phario\\phive\\pharioaliasresolver' => '/src/services/resolver/PharIoAliasResolver.php', + 'phario\\phive\\phariorepository' => '/src/shared/repository/PharIoRepository.php', + 'phario\\phive\\pharregistry' => '/src/shared/PharRegistry.php', + 'phario\\phive\\pharregistryexception' => '/src/shared/exceptions/PharRegistryException.php', + 'phario\\phive\\pharservice' => '/src/services/phar/PharService.php', + 'phario\\phive\\pharurl' => '/src/shared/phar/PharUrl.php', + 'phario\\phive\\phivecontext' => '/src/PhiveContext.php', + 'phario\\phive\\phiveversion' => '/src/shared/version/PhiveVersion.php', + 'phario\\phive\\phivexmlconfig' => '/src/shared/config/PhiveXmlConfig.php', + 'phario\\phive\\phivexmlconfigfilelocator' => '/src/shared/config/PhiveXmlConfigFileLocator.php', + 'phario\\phive\\projectphivexmlmigration' => '/src/services/migration/ProjectPhiveXmlMigration.php', + 'phario\\phive\\publickey' => '/src/services/key/PublicKey.php', + 'phario\\phive\\publickeyexception' => '/src/shared/exceptions/PublicKeyException.php', + 'phario\\phive\\publickeyreader' => '/src/services/key/gpg/PublicKeyReader.php', + 'phario\\phive\\purgecommand' => '/src/commands/purge/PurgeCommand.php', + 'phario\\phive\\purgecontext' => '/src/commands/purge/PurgeContext.php', + 'phario\\phive\\ratelimit' => '/src/shared/http/RateLimit.php', + 'phario\\phive\\release' => '/src/shared/phar/Release.php', + 'phario\\phive\\releasecollection' => '/src/shared/phar/ReleaseCollection.php', + 'phario\\phive\\releaseexception' => '/src/shared/exceptions/ReleaseException.php', + 'phario\\phive\\releaseselector' => '/src/services/phar/ReleaseSelector.php', + 'phario\\phive\\remotefirstresolvingstrategy' => '/src/services/resolver/strategy/RemoteFirstResolvingStrategy.php', + 'phario\\phive\\remotesourceslistfileloader' => '/src/shared/sources/RemoteSourcesListFileLoader.php', + 'phario\\phive\\removalservice' => '/src/services/phar/RemovalService.php', + 'phario\\phive\\removecommand' => '/src/commands/remove/RemoveCommand.php', + 'phario\\phive\\removecommandconfig' => '/src/commands/remove/RemoveCommandConfig.php', + 'phario\\phive\\removecontext' => '/src/commands/remove/RemoveContext.php', + 'phario\\phive\\requestedphar' => '/src/shared/phar/RequestedPhar.php', + 'phario\\phive\\requestedpharresolver' => '/src/services/resolver/RequestedPharResolver.php', + 'phario\\phive\\requestedpharresolverfactory' => '/src/services/resolver/RequestedPharResolverFactory.php', + 'phario\\phive\\requestedpharresolverservice' => '/src/services/resolver/RequestedPharResolverService.php', + 'phario\\phive\\requestedpharresolverservicebuilder' => '/src/services/resolver/RequestedPharResolverServiceBuilder.php', + 'phario\\phive\\resetcommand' => '/src/commands/reset/ResetCommand.php', + 'phario\\phive\\resetcommandconfig' => '/src/commands/reset/ResetCommandConfig.php', + 'phario\\phive\\resetcontext' => '/src/commands/reset/ResetContext.php', + 'phario\\phive\\resolveexception' => '/src/shared/exceptions/ResolveException.php', + 'phario\\phive\\resolvingstrategy' => '/src/services/resolver/strategy/ResolvingStrategy.php', + 'phario\\phive\\retryinghttpclient' => '/src/shared/http/RetryingHttpClient.php', + 'phario\\phive\\ringdowncurlhttpclient' => '/src/shared/http/RingdownCurlHttpClient.php', + 'phario\\phive\\selfupdatecommand' => '/src/commands/selfupdate/SelfupdateCommand.php', + 'phario\\phive\\sha1hash' => '/src/shared/hash/sha/Sha1Hash.php', + 'phario\\phive\\sha256hash' => '/src/shared/hash/sha/Sha256Hash.php', + 'phario\\phive\\sha384hash' => '/src/shared/hash/sha/Sha384Hash.php', + 'phario\\phive\\sha512hash' => '/src/shared/hash/sha/Sha512Hash.php', + 'phario\\phive\\signatureverifier' => '/src/services/signature/SignatureVerifier.php', + 'phario\\phive\\skelcommand' => '/src/commands/skel/SkelCommand.php', + 'phario\\phive\\skelcommandconfig' => '/src/commands/skel/SkelCommandConfig.php', + 'phario\\phive\\skelcontext' => '/src/commands/skel/SkelContext.php', + 'phario\\phive\\source' => '/src/shared/sources/Source.php', + 'phario\\phive\\sourcerepository' => '/src/shared/repository/SourceRepository.php', + 'phario\\phive\\sourceslist' => '/src/shared/sources/SourcesList.php', + 'phario\\phive\\sourceslistexception' => '/src/shared/exceptions/SourcesListException.php', + 'phario\\phive\\sourceslistfileloader' => '/src/shared/sources/SourcesListFileLoader.php', + 'phario\\phive\\staticphiveversion' => '/src/shared/version/StaticPhiveVersion.php', + 'phario\\phive\\statuscommand' => '/src/commands/status/StatusCommand.php', + 'phario\\phive\\statuscommandconfig' => '/src/commands/status/StatusCommandConfig.php', + 'phario\\phive\\statuscontext' => '/src/commands/status/StatusContext.php', + 'phario\\phive\\supportedrelease' => '/src/shared/phar/SupportedRelease.php', + 'phario\\phive\\targetdirectorylocator' => '/src/shared/TargetDirectoryLocator.php', + 'phario\\phive\\tokenauthentication' => '/src/shared/http/authentication/TokenAuthentication.php', + 'phario\\phive\\trustedcollection' => '/src/services/key/TrustedCollection.php', + 'phario\\phive\\unixoidenvironment' => '/src/shared/environment/UnixoidEnvironment.php', + 'phario\\phive\\unixoidpharinstaller' => '/src/services/phar/UnixoidPharInstaller.php', + 'phario\\phive\\unsupportedrelease' => '/src/shared/phar/UnsupportedRelease.php', + 'phario\\phive\\unsupportedversionconstraintexception' => '/src/shared/exceptions/UnsupportedVersionConstraintException.php', + 'phario\\phive\\updatecommand' => '/src/commands/update/UpdateCommand.php', + 'phario\\phive\\updatecommandconfig' => '/src/commands/update/UpdateCommandConfig.php', + 'phario\\phive\\updatecontext' => '/src/commands/update/UpdateContext.php', + 'phario\\phive\\updaterepositorylistcommand' => '/src/commands/update-repository-list/UpdateRepositoryListCommand.php', + 'phario\\phive\\url' => '/src/shared/Url.php', + 'phario\\phive\\urlrepository' => '/src/shared/repository/UrlRepository.php', + 'phario\\phive\\usedphar' => '/src/shared/phar/UsedPhar.php', + 'phario\\phive\\userfilemigration' => '/src/services/migration/UserFileMigration.php', + 'phario\\phive\\verificationfailedexception' => '/src/shared/exceptions/VerificationFailedException.php', + 'phario\\phive\\verificationresult' => '/src/services/signature/VerificationResult.php', + 'phario\\phive\\versioncommand' => '/src/commands/version/VersionCommand.php', + 'phario\\phive\\windowsenvironment' => '/src/shared/environment/WindowsEnvironment.php', + 'phario\\phive\\windowspharinstaller' => '/src/services/phar/WindowsPharInstaller.php', + 'phario\\phive\\xmlfile' => '/src/shared/XmlFile.php', + 'phario\\version\\abstractversionconstraint' => '/vendor/phar-io/version/src/constraints/AbstractVersionConstraint.php', + 'phario\\version\\andversionconstraintgroup' => '/vendor/phar-io/version/src/constraints/AndVersionConstraintGroup.php', + 'phario\\version\\anyversionconstraint' => '/vendor/phar-io/version/src/constraints/AnyVersionConstraint.php', + 'phario\\version\\buildmetadata' => '/vendor/phar-io/version/src/BuildMetaData.php', + 'phario\\version\\exactversionconstraint' => '/vendor/phar-io/version/src/constraints/ExactVersionConstraint.php', + 'phario\\version\\exception' => '/vendor/phar-io/version/src/exceptions/Exception.php', + 'phario\\version\\greaterthanorequaltoversionconstraint' => '/vendor/phar-io/version/src/constraints/GreaterThanOrEqualToVersionConstraint.php', + 'phario\\version\\invalidprereleasesuffixexception' => '/vendor/phar-io/version/src/exceptions/InvalidPreReleaseSuffixException.php', + 'phario\\version\\invalidversionexception' => '/vendor/phar-io/version/src/exceptions/InvalidVersionException.php', + 'phario\\version\\nobuildmetadataexception' => '/vendor/phar-io/version/src/exceptions/NoBuildMetaDataException.php', + 'phario\\version\\noprereleasesuffixexception' => '/vendor/phar-io/version/src/exceptions/NoPreReleaseSuffixException.php', + 'phario\\version\\orversionconstraintgroup' => '/vendor/phar-io/version/src/constraints/OrVersionConstraintGroup.php', + 'phario\\version\\prereleasesuffix' => '/vendor/phar-io/version/src/PreReleaseSuffix.php', + 'phario\\version\\specificmajorandminorversionconstraint' => '/vendor/phar-io/version/src/constraints/SpecificMajorAndMinorVersionConstraint.php', + 'phario\\version\\specificmajorversionconstraint' => '/vendor/phar-io/version/src/constraints/SpecificMajorVersionConstraint.php', + 'phario\\version\\unsupportedversionconstraintexception' => '/vendor/phar-io/version/src/exceptions/UnsupportedVersionConstraintException.php', + 'phario\\version\\version' => '/vendor/phar-io/version/src/Version.php', + 'phario\\version\\versionconstraint' => '/vendor/phar-io/version/src/constraints/VersionConstraint.php', + 'phario\\version\\versionconstraintparser' => '/vendor/phar-io/version/src/VersionConstraintParser.php', + 'phario\\version\\versionconstraintvalue' => '/vendor/phar-io/version/src/VersionConstraintValue.php', + 'phario\\version\\versionnumber' => '/vendor/phar-io/version/src/VersionNumber.php' + ); + } + + $class = strtolower($class); + + if (isset($classes[$class])) { + require 'phar://phive.phar/' . $classes[$class]; + } + } +); + +Phar::mapPhar('phive.phar'); + +$rc = (new Factory(new Cli\Request($_SERVER['argv']), new StaticPhiveVersion('0.15.3')))->getRunner()->run(); +exit($rc); + +__HALT_COMPILER(); ?> +nX= +phive.phar+vendor/phar-io/filesystem/src/Directory.phpf4vendor/phar-io/filesystem/src/DirectoryException.phpf=v"+vendor/phar-io/filesystem/src/Exception.phpff^$&vendor/phar-io/filesystem/src/File.phpffC+r*vendor/phar-io/filesystem/src/Filename.php fVs)3vendor/phar-io/filesystem/src/FilenameException.phpmfbfo2vendor/phar-io/filesystem/src/LastModifiedDate.phpfT[(vendor/phar-io/executor/src/Executor.phpf#j~1vendor/phar-io/executor/src/ExecutorException.phpfg13,vendor/phar-io/version/src/VersionNumber.phpfkJ}r6vendor/phar-io/manifest/src/ManifestDocumentMapper.phpfq.vendor/phar-io/manifest/src/ManifestLoader.phpfq̤2vendor/phar-io/manifest/src/ManifestSerializer.phpfMۂEvendor/phar-io/manifest/src/exceptions/ElementCollectionException.phpf0_Uj4vendor/phar-io/manifest/src/exceptions/Exception.phpfPJvendor/phar-io/manifest/src/exceptions/InvalidApplicationNameException.php1fFMV@vendor/phar-io/manifest/src/exceptions/InvalidEmailException.phpf+ˤ>vendor/phar-io/manifest/src/exceptions/InvalidUrlException.phpf/YDvendor/phar-io/manifest/src/exceptions/ManifestDocumentException.phpf' oKvendor/phar-io/manifest/src/exceptions/ManifestDocumentLoadingException.phpf!գJvendor/phar-io/manifest/src/exceptions/ManifestDocumentMapperException.phpf0`'iCvendor/phar-io/manifest/src/exceptions/ManifestElementException.phpf%#mߤBvendor/phar-io/manifest/src/exceptions/ManifestLoaderException.phpf4oBvendor/phar-io/manifest/src/exceptions/NoEmailAddressException.phpf4q:2vendor/phar-io/manifest/src/values/Application.phpf5f=6vendor/phar-io/manifest/src/values/ApplicationName.phpf-vendor/phar-io/manifest/src/values/Author.phpfvendor/phar-io/manifest/src/values/PhpExtensionRequirement.phpfw(<vendor/phar-io/manifest/src/values/PhpVersionRequirement.php$f"&2vendor/phar-io/manifest/src/values/Requirement.phpf 1q<vendor/phar-io/manifest/src/values/RequirementCollection.php\fX?Dvendor/phar-io/manifest/src/values/RequirementCollectionIterator.phpf +vendor/phar-io/manifest/src/values/Type.phpf<2*vendor/phar-io/manifest/src/values/Url.phpf#lͣ1vendor/phar-io/manifest/src/xml/AuthorElement.phpfn2;vendor/phar-io/manifest/src/xml/AuthorElementCollection.phpSfS(72vendor/phar-io/manifest/src/xml/BundlesElement.phpzff}4vendor/phar-io/manifest/src/xml/ComponentElement.phpf`$Sg>vendor/phar-io/manifest/src/xml/ComponentElementCollection.php\fT6 3vendor/phar-io/manifest/src/xml/ContainsElement.phpfaΤ4vendor/phar-io/manifest/src/xml/CopyrightElement.php f}t)T5vendor/phar-io/manifest/src/xml/ElementCollection.phpf砲L.vendor/phar-io/manifest/src/xml/ExtElement.phpfK8vendor/phar-io/manifest/src/xml/ExtElementCollection.phpJfQ4vendor/phar-io/manifest/src/xml/ExtensionElement.phpfazj2vendor/phar-io/manifest/src/xml/LicenseElement.php|f\tTX4vendor/phar-io/manifest/src/xml/ManifestDocument.php f9S3vendor/phar-io/manifest/src/xml/ManifestElement.phpnfp.vendor/phar-io/manifest/src/xml/PhpElement.phpfwO3vendor/phar-io/manifest/src/xml/RequiresElement.phpKf^ܗ)src/commands/composer/ComposerContext.phpfx)src/commands/composer/ComposerCommand.phpf`^/src/commands/composer/ComposerCommandConfig.phpfl Ť)src/commands/composer/ComposerService.php f,'src/commands/default/DefaultCommand.php,fe-src/commands/default/DefaultCommandConfig.phpft@ksrc/commands/help/help.md +f$4w!src/commands/help/HelpCommand.phpfJD6src/commands/install/InstallCommandConfigException.phpf2='src/commands/install/InstallContext.phpNf|S-src/commands/install/InstallCommandConfig.php{fk'src/commands/install/InstallCommand.php +f;!src/commands/list/ListCommand.phpfD|'src/commands/migrate/MigrateContext.phpfPX'e'src/commands/migrate/MigrateCommand.phpf "-src/commands/migrate/MigrateCommandConfig.phptftW¤1src/commands/outdated/OutdatedConfigException.phpf-4)src/commands/outdated/OutdatedContext.php4f#>(src/commands/outdated/OutdatedConfig.php9f )src/commands/outdated/OutdatedCommand.phpQfPK5#src/commands/purge/PurgeContext.phpffP#src/commands/purge/PurgeCommand.phpsf(Ҥ%src/commands/remove/RemoveContext.phpfQ'%src/commands/remove/RemoveCommand.php f0M+src/commands/remove/RemoveCommandConfig.phpf/@#src/commands/reset/ResetContext.phpfEŴ#src/commands/reset/ResetCommand.php0fr#\)src/commands/reset/ResetCommandConfig.phpfIŤ-src/commands/selfupdate/SelfupdateCommand.phpfgYU!src/commands/skel/SkelContext.php2fX_Mʹ'src/commands/skel/SkelCommandConfig.phpf!E!src/commands/skel/SkelCommand.phph fV%src/commands/status/StatusContext.phpft&%src/commands/status/StatusCommand.php f+src/commands/status/StatusCommandConfig.phpI +f<U氝Csrc/commands/update-repository-list/UpdateRepositoryListCommand.phpf8Hj%src/commands/update/UpdateContext.phpfzf%src/commands/update/UpdateCommand.phpfk+src/commands/update/UpdateCommandConfig.php f@$'src/commands/version/VersionCommand.php fOsrc/commands/CommandLocator.phpq +fX@)src/services/checksum/ChecksumService.php8f 2w+src/services/key/gpg/GnupgKeyDownloader.phpt +fݴ)src/services/key/gpg/GnupgKeyImporter.phpf~(src/services/key/gpg/PublicKeyReader.phpf5W"src/services/key/KeyDownloader.phpf}.e src/services/key/KeyImporter.phpfj$src/services/key/KeyImportResult.phpafAGsrc/services/key/KeyService.php fUsrc/services/key/PublicKey.phpfr:F?&src/services/key/TrustedCollection.phpfa~ (src/services/migration/FileMigration.phpft ;Y0src/services/migration/HomePhiveXmlMigration.php>f?겴0src/services/migration/InternalFileMigration.phpYfm@$src/services/migration/Migration.phpfX'mY3src/services/migration/ProjectPhiveXmlMigration.phpf;9̴,src/services/migration/UserFileMigration.phpf`p0src/services/migration/HomePharsXmlMigration.phplf]]&+src/services/migration/MigrationFactory.phpfrA+src/services/migration/MigrationService.phpfH4$src/services/phar/PharDownloader.phpVfx^#src/services/phar/PharInstaller.php f 5JԴ*src/services/phar/PharInstallerFactory.phpf *src/services/phar/PharInstallerLocator.php-fV!src/services/phar/PharService.phpfs%src/services/phar/ReleaseSelector.php fm*src/services/phar/UnixoidPharInstaller.php7fn0ޱ*src/services/phar/WindowsPharInstaller.phpfG$src/services/phar/InstallService.phpv f*src/services/phar/CompatibilityService.php f>~^$src/services/phar/RemovalService.phpuf%<src/services/resolver/strategy/AbstractResolvingStrategy.php f+ז >src/services/resolver/strategy/LocalFirstResolvingStrategy.php]fqM;B?src/services/resolver/strategy/RemoteFirstResolvingStrategy.php^frٴ4src/services/resolver/strategy/ResolvingStrategy.phpf-W$7src/services/resolver/AbstractRequestedPharResolver.php0f82Q+src/services/resolver/DirectUrlResolver.phpf7k-src/services/resolver/GitlabAliasResolver.phpGfv㵴,src/services/resolver/LocalAliasResolver.phpf_-src/services/resolver/PharIoAliasResolver.php fIDX/src/services/resolver/RequestedPharResolver.php4fWR/6src/services/resolver/RequestedPharResolverFactory.php4f.?L6src/services/resolver/RequestedPharResolverService.phpWfzN=src/services/resolver/RequestedPharResolverServiceBuilder.phpf+&-src/services/resolver/GithubAliasResolver.phpt f@Zt6src/services/signature/gpg/GnupgVerificationResult.phpfkL5src/services/signature/gpg/GnupgSignatureVerifier.php8fe '8,src/services/signature/SignatureVerifier.phpf>P-src/services/signature/VerificationResult.php4fJOsrc/shared/cli/input/Input.phpf()DU%src/shared/cli/input/ConsoleInput.phpfK﨤'src/shared/cli/output/ConsoleOutput.php +fyT:|&src/shared/cli/output/ConsoleTable.phpfrd#; src/shared/cli/output/Output.phpf^J'src/shared/cli/output/OutputFactory.phpf[Q@.src/shared/cli/output/ColoredConsoleOutput.php[f;b'src/shared/cli/output/OutputLocator.phpgf Asrc/shared/cli/Command.phpfǯG*src/shared/cli/CommandLocatorException.phpf&t*src/shared/cli/CommandOptionsException.phpf<@src/shared/cli/Context.phpzf4nN#src/shared/cli/ContextException.phpf/U#src/shared/cli/RequestException.phpfGMꪴ"src/shared/cli/RunnerException.phpfm;gsrc/shared/cli/error.txtRf'!src/shared/cli/CommandLocator.phpf7o6!src/shared/cli/GeneralContext.phpB fsrc/shared/cli/Options.phpf6src/shared/cli/Request.phpfu7Nsrc/shared/cli/Runner.phpVfE0N src/shared/config/AuthConfig.phpf,d#src/shared/config/AuthXmlConfig.php fS঴.src/shared/config/AuthXmlConfigFileLocator.phpxff6ô)src/shared/config/CompositeAuthConfig.phpf1z*src/shared/config/GlobalPhiveXmlConfig.php@f_fK[)src/shared/config/LocalPhiveXmlConfig.phpfqٴ/src/shared/config/PhiveXmlConfigFileLocator.php)fWQwŴ+src/shared/config/EnvironmentAuthConfig.phpfVo$src/shared/config/PhiveXmlConfig.php#fh} Msrc/shared/config/Config.phpf5&src/shared/download/FileDownloader.phpj fFcz-src/shared/environment/EnvironmentLocator.phpf-src/shared/environment/WindowsEnvironment.phpf |-src/shared/environment/UnixoidEnvironment.php +fRs&src/shared/environment/Environment.php=f'f)'src/shared/exceptions/AuthException.phpf Z +P)src/shared/exceptions/ConfigException.phpf .´-src/shared/exceptions/CurlConfigException.phpf?c'src/shared/exceptions/CurlException.phpf α1src/shared/exceptions/DownloadFailedException.phpf˫)$.src/shared/exceptions/EnvironmentException.phpfbb(src/shared/exceptions/ErrorException.phpf IW#src/shared/exceptions/Exception.phpf +<+src/shared/exceptions/ExecutorException.phpf.1src/shared/exceptions/FeatureMissingException.phpf2src/shared/exceptions/FileNotWritableException.phpfg )&src/shared/exceptions/GitException.phpf +=5src/shared/exceptions/GnupgKeyDownloaderException.phpfm8%src/shared/exceptions/IOException.phpf +˸˴5src/shared/exceptions/InstallationFailedException.phpfu.src/shared/exceptions/InvalidHashException.phpf`o-src/shared/exceptions/InvalidXmlException.phpf}5src/shared/exceptions/LinkCreationFailedException.phpfH@!g,src/shared/exceptions/MigrationException.phpf g3src/shared/exceptions/MigrationsFailedException.phpftshg3src/shared/exceptions/NoGPGBinaryFoundException.phpf G?+src/shared/exceptions/NotFoundException.phpfA)'src/shared/exceptions/PharException.phpf +2eh״0src/shared/exceptions/PharInstallerException.phpfB/src/shared/exceptions/PharRegistryException.phpfQz-,src/shared/exceptions/PublicKeyException.phpfL*src/shared/exceptions/ReleaseException.phpf |*src/shared/exceptions/ResolveException.phpf Db.src/shared/exceptions/SourcesListException.phpf)z/?src/shared/exceptions/UnsupportedVersionConstraintException.phpfR5src/shared/exceptions/VerificationFailedException.phpfV& src/shared/executor/Executor.phpfZ'&/&src/shared/executor/ExecutorResult.php"f8 src/shared/hash/sha/Sha1Hash.phpLf|"src/shared/hash/sha/Sha256Hash.phpRf$U"src/shared/hash/sha/Sha384Hash.phpRf-G"src/shared/hash/sha/Sha512Hash.phpSfCcsrc/shared/hash/BaseHash.phpNfqQsrc/shared/hash/Hash.phpfB. 6src/shared/http/authentication/BasicAuthentication.phpfB-7src/shared/http/authentication/BearerAuthentication.phpf/'I6src/shared/http/authentication/TokenAuthentication.phpf/E src/shared/http/CacheBackend.phpLfS4src/shared/http/Curl.phpu +flBi!src/shared/http/HttpException.phpffd_'src/shared/http/HttpProgressHandler.phpf Y_)src/shared/http/HttpResponseException.phpfmȑA"src/shared/http/Authentication.phpfsrc/shared/http/CurlConfig.phpGf %src/shared/http/CurlConfigBuilder.phpf src/shared/http/ETag.php2fPxpr+src/shared/http/FileStorageCacheBackend.phpf9[i(src/shared/http/HttpProgressRenderer.php +f P&src/shared/http/HttpProgressUpdate.phpf'mKdĤ'src/shared/http/LocalSslCertificate.phpfۥsrc/shared/http/RateLimit.php(f$"src/shared/http/CurlHttpClient.phpf{¦src/shared/http/HttpClient.phpf1K e src/shared/http/HttpResponse.phpf|&src/shared/http/RetryingHttpClient.phpfp+@*src/shared/http/RingdownCurlHttpClient.php fn%ri+src/shared/phar/ConfiguredPharException.phpf/4@ý"src/shared/phar/PharIdentifier.phpflIsrc/shared/phar/PharUrl.phpf]\src/shared/phar/Release.phpf8Eȴ%src/shared/phar/ReleaseCollection.phpHfcG!src/shared/phar/InstalledPhar.php?f)7src/shared/phar/PharAlias.phpvfmy&src/shared/phar/UnsupportedRelease.phpf- "src/shared/phar/ConfiguredPhar.php3 fosrc/shared/phar/Phar.phpf=@j!src/shared/phar/RequestedPhar.php +f0$src/shared/phar/SupportedRelease.phpfO1src/shared/phar/UsedPhar.phpvf<*src/shared/repository/SourceRepository.phpf*dд*src/shared/repository/GitlabRepository.phpI +f 2t)src/shared/repository/LocalRepository.phpf im*src/shared/repository/PharIoRepository.php( +f{'src/shared/repository/UrlRepository.phpfV$t*src/shared/repository/GithubRepository.php +fR'!r,src/shared/sources/SourcesListFileLoader.phpf9>K1src/shared/sources/LocalSourcesListFileLoader.phpCfnCҤ2src/shared/sources/RemoteSourcesListFileLoader.phpfsSDsrc/shared/sources/Source.php\f [ "src/shared/sources/SourcesList.php f!dN+src/shared/version/GitAwarePhiveVersion.phpf%BǴ#src/shared/version/PhiveVersion.phpfL++)src/shared/version/StaticPhiveVersion.phpaf`Vkشsrc/shared/ComposerAlias.php^fI&src/shared/FileDownloaderException.phpffsrc/shared/Git.phpVf5<Ӵsrc/shared/JsonData.phpMf讛 %src/shared/TargetDirectoryLocator.phpfyH>Ofsrc/shared/Url.phpm +f ôsrc/shared/PharRegistry.php~&fc src/shared/XmlFile.phpm f8jsrc/shared/GnuPG.phpXf0$src/GithubAliasResolverException.phpfQsrc/PhiveContext.phpf4ksrc/autoload.phpSf1 "w|src/Factory.phpFf +(conf/auth.skeleton.xmldfR&ٴ conf/auth.xsdf:%conf/pgp-keyservers.phpYfJ+pconf/pharBat.template%f'conf/phive.skeleton.xmlfRWmoF _v$et:s"M)DYHw$h߇;Xd7OD>$RO B դx@sP `)1SOý&L{ aZ[0 KS`|{;S|1 ,_$<(q)`>Фۚ{ڛ_b'(t\x܅Yr[ -݂zrK^"rεx0w`ZoUPlxllIf u1@I6ɨ.KdM S(L.6"113'QWMEW@H1L Sk `6Q?ȟ!KXR!zBZL)v?1ȭjiWDcw%<]”qQ{dYh9׆3d.aIqV;î˴X)BY!L.;魓X'm[fW14$`xN69ca 5h39^7B!i?zuj?? ys?+oVak}H75(­ͣ壖{"yˍ ko +AlنWZ!Mzwb%cQqqV,u8?N4 ҷm= 0~ō::Air4] +/uZ&o2OD#hI,XlZA0=K2xLnqE +Ls I] ȃr@DLw(nhyUb(7yXh?bG7Ͳx Eȱ +@ =O8Mp$<8pɠz[hGMs玟MKE6Uw>)c<=Dŝ߇"qiɺMG}=>'Z.*N6o}kkܟ ajMqg!Yϝ2>C>N30Cviۏ8ΰmiqh Qq'ĢęqeiIXXZ徫9ju*$% Iy* +r1$9MID~FJ4"WMJ/[oJS6"4iKu"ipt6޵v!w|6 /z7;߽Oܧ 4HM|[ |r43쒵_l2g&l9^I$a*}Jf+iH>܃_)*(in&3*FJ3Y KzRJsB.pS՘ /\r5'$CpJZŮtZSs]#8@*AP;f[&£ O}A_?Y[O\OJ]i_%&4BmM)?EZ@xuLY? ExszvU˴ :P 4_*{d:LΚcSzڮTuζxgʧY-9/T5XI5vڴ#rXE>.ۣd,J~gmnW+*Ҹ{4v؏I4ΙWppxs83δcp5k^EgM|;n⩦Q-Ns{98r6MeO +|A[́G[$WmFSӪ7}?O0wJT({ U@0\4:*叓6 %ݽM,G)<\2&R7|Jpb `E <8#Tz__-Ӣ:pp%Lq[;9"6O>i%Vf'(hG% rY=@3Wh4mOm|{-H M,U>0oK]éVԧDYIUPp;PIQ֘tt2޽ҹy׵cB!%`Nž/e#ЧpSU'Y~\m6>,nbImKN.w$9@˴mTHE(b{n>Ib<ݱ#;V7T(^5i=^}BЖ֌z+G݋uC&ojcM!| yO;r`)j؎2.?̣T1cɞtoX7 )o-P{F0,`TcX1ot۰˳pI򜦓H\k{dG fj11D]'&pفX˝լڼeEaJ$ܡ˃+*`®wJ>0)>J h:C:'ipZ&f@.oC+3GD0:*-濌 =RkNC(ڴ'pfi@µӝd'CLz!Q:Ư-"ԛr(/c5\ruO/Q>9#>\ҤUEož&'zKR9)F%`~U!u 8_/Ue믃׳#,W8ю[t#3={kʛO}|]  6xYg{j8W*jw/[,"L{B*@@6[ϋlZeaGYO{3M µ:WxR@ydM;kRCPCױ +[O"esEr0 &Ay$ ;$2% dҐI@gLl¨thM\O}4tȺ^GhxN̐=\cJE :b .<[p /d\ Tݱ2"]4qG [Ƽx̿6_V4 ̦FHX.+I=eFrf\FHyP2D;v)*H.`%j#tgs/Xj]`gT<%J.|d֚$JL=HtȽ{.KM2פ=aȣyTCIT?PPb0ef=B/Q7/C6aۘdCE":]w1#/ۘ 1]W_&e}#!;3pt ?3KϏGNȑs^CYG~չ;)>ϏEm@>똩.ZzG< Ֆv(];$_ΉC:5*#7GBmzkz2!% >+ ?F/ZۻYe+kց4 SLFi(UQCl8 ~Cs’KKNpZ"#ƾKŖ [~o BU1j5InX3l9<=뜲q'w <*%3n>6,;g%31avnmSr2&[uOUp [֫\W>ﮪo +Eu.ܻ!"kbMRG.vq&[4_\]Ty38@>TtjЈFhFk.@[v 7qQJX-\q!4Yh){3;tI +]'3 +l}V'B P3dH r'ܵ[|C@+?V>MEƸ'pQFF-.LlMMQ3Λm ;f pe޼m& Q9*z v;7Fh$χ:eB?2B=U]@"$_?BH]DaC.P*լ7qObVw;Ag0?ZKX34.r|fyl|zWlD9{9WH!CV˦W"q($ cү_̵ɮe[= [JxB?z#({6<7𩦈rGƣRUɞ7A(Waui]Ȩǝs{:s픐NӈB[8[K\iY h|OW:Qh̶W.ѝ:ḍ.#)]o"|:AbY^`edTKt: 6^2*^NywLjLL"oUU!ZHdh>[jv +젱S|^seu,mΞ2?; +SUʏD9ڱ=5_ՠuJv P艶_g9xS-=9 v!Ɛ< ++cO\-)f\K@m8Չvj;z/ya % {W1L֓*[VdT?fWYZFz(6F9@8*CvrMF"K_*y J1\sAV?07RHƟF MRHTM#)SnQ\XYSdͣ1mT*//=fo Fn7h [+Wf]aqӾQq%On^J8㽙 lBxڤY +j~/ݶIw{"+*' S!?Qيxu.QtF*U;%8qO^larHWFW]]F/1z}Bh(^B]FW_F1xF: NX3̰8gęay3-h%Z<# tED-=j_.b?@l\aT÷_Qe(`(7^!WFV*CAb9cwS/Ǟ76U{.v^/Hjh9/5(* yZ|\Ҽ6tcEj-R4'o^ϋyVwl Q@ +b~Q:޶i,A{[[lդ 8L%t]UQ V8Ose5>pm -=@%@&)}س1|Fy'po 7+kG ANR ?IRM JʮDF棞_vFxe,~CM}'>;27dl3>Dd3R/xbs2,PYj63sMV "M2hoH,ܷ/ ؈ hlHB~A{_̼ԬmKY_pgH/9zΧxy/xE1 +0 =Ȩ$Pt*ᝒic_V1l*kt)Ry4E};#Jvp>uT]o0}ϯ@=V(E(*L.R-)>9_@H>{V&T!FDf'Qz5jIcYBDct}4bKqO06B>_q8.q&8y/RhTAsӽvj 4{{ +i/b tVrqjs,/tk_ڨn]T](eo)a"(}cԃ tLtr`Bj0;DzQDbx +qWzǹ 'x{6~|5oP=7-Ɩ2h4Pk@R`'OWZ +Mxn}?`Xj&Tvx6.0&kYSq;!:.6"<-FjM+by)ǸnLFh#xRy|u:"+%gs8Bh7)4 gyg2u ܂IPaB`w,N)Yj7bLB-fI+ Wx>+K:e^,9y9%\dEؕSYa&qr[Rd|=)R`_E܁P64M$Uw*۷8 g@V|!+ߚr=lUcɱ_c5l _]<5h:Yh&2т;{?ks6~ތ;|%n˖'vs58I1 hG|$Gnzb}b͛tB~D8Br˅ܤ(` uEJ|5=|t2QmE? ~\~FE3q#$Ƴ;ї BP?TםNeMP\ /)r 6=HXX1uGF9@.ӄPJzD6] i12A~r?/kF$Md,DȒ,\Ln&pEJ8$rҌ@с~Ǐ]v:SxL8nPx +rL$ VޫTiQ K&t'UQrDJ$@7,lQV ?aB̗ne4[=C&5@2]SsWD&%|slY1.1 C4PMi4j 욈 +Mn}pg?3ឝvNff'+DӢGquuʹ8:&R&9&YQ"O9eQ$<ߪmh)y<|ol#QXk"ֻSl)ܤ꾷J-&˸oUD^̯xۧoe aɑ<5`\ި)VW?1K"$% !c܈[Qn{b,DH.6?Nrr%4nnTSLn'Pyp?ё7&xu(4篏Œ8Vec{c?TnaЀWqrV ļ9 +M\i+6-P;|QdXa:U9fހrp2;=myd-#Hn4o ɋH4jKT( h!GV򑗰RN%zk.Հh=_f]NW~%L8[tmTyVB.I#?'XϿfQz"Jÿ{PtepX90seQ䘭I߫[~p>9n~ݍo9`mNuB:Nai5n;m3SҺz2j<Ë_#*Zn΅ E&Kܾ&˛?h:vv=i&LmY_X_Dp1ܷTo9 Ճ0/-ZT3Z}[Rv_pyM:!d"e׫q/y4n D25jIFaŶex)bq*0]xR[MsB*tF;`>s:Wbj-b!VtwrtC[u.,x5JTWo[cp'-v]=IJ404λǝ{ѫԨ]+m3>ʥ/Suz_EHZE^=R>8\;鶛Vh4;JbXJ0+O-~_T|%2mzgWTx!N_Qu~/QI$.rCi%&4r,9jݑiH";߷z*3R\7 vˈnS*`mIi8#> jŬr"øˆn`"ckx}pũE&ؙT!eWE3/8[0V6i>3h#hS$d\"?]XNo``<b%s^!c-%FO>=OՑby Enw o5et ֩yFn6#ٚG ڟ0Z= ! 4tN9zקںzP~o%}9 &Xyo;k^t#brwzmǾ;!jq(FTI||PmP]kA |_1&$5v|@)>ޞ']$])S=IAͧWrTZ+'Jx\U +?z6t lQ>SyIj\*`.w*76Qn|Kl_h$Nm+xOwz=<~>NVG>ZiF{>beDH) q 1Y@Rfz(bC4O:6Qt9RjfQw/ RQ6*pi=ῖѶ,6woM.V'mAo1+!RwK+Ɇ6%J8!YllؖgiѦI"|G{Mhx\fg>'x[Mns"vf)?ğ?8a\`] +6ܐOsTds^)3Nt1C-뽇9UPh פ.+$$GǛr1X6VK Z7-6N-thEb Ė +n@kD +$c䂂C+k?ΠK`rޡeEQ"׳]Xդx]Zy|>RYU''o.07L{=#9F)I/m-mn@~9pJDT@9EBG]ngШɻWkB{~{9TJ2Q9P)|o *aK +?=D:nYtI4Ѽ8\_4’ +5$0< o+t%X6"B`Ǵop\$UV%~F=kZC`|IQ氦YA7Yf,_PIVӠ]. ;EHFw()B*Af5ڷݦl`8Zڴh-G 3ah(t<>fu08s_t. +I8!m mPݸt:}4rbxa?uQAn0sA6n$HE[ A.i`E&%e(ڊQٙ=JVE;~8& #<:҆;}9)F<6PfH#ak|moOXs] Cƍ&} 7؜+ (d[`iްȘ lk$&hg n˶ SAbbjwŚ!f +!.QıTٴ+$6`[V*v)(hRJSX9VRz/M tkMDȦ +XnDſ + X#I卥#"j-$CSRѓ⋀E ňg>N;gVmqS%Dɿ&l,c>= c'Eqhf*mNeAIqbn%Hfi"KQN*T7%AV̥D.V'T~;:X/Dٲf<43ċ+i'Me_E= 0=F89t+ +N9 G.R\qVŷW(.t:.s){дrD5)ypG619J*`c[MJ&$iDBnF{~Eʱ +0=OqUSA둻JE|w +]?YGXagrF': \atpGy~{nCp͙ +.XYj>x8;Ƃ4H sH4}b=&jCi;F'*ۛzY8F lg3{l5FbJEig2ѫ$ :AM2Rw2k--ؿW<9.ׁA?f ѩfNp|ݿ?}Ao1+!lTqmETR4Z8UU;64|oM#Z6I}^׳j:0]o:6!(B=m%/+ ұVX;L"=3Lb| g İ #_ :[> Ono +jX{R(&+8X - +H +UqVٺ3+'%DŸTBϖS>d9pJ}3tQ.J^(Rk7JM]N=pSeN#}.863l֨ O3Ԫ!ܚD{ w"m~P_kfk"3gZٸU&|&al&T5gj)QiL +MD\xBkc:blڼL=*bLPH DŽ +_k;:MswY󁋺Q8jt/}1v?N']]=mvTXS7fSBeRQ&oQ#'{Ӧ1Sq ) @gs`^;X**+haO! Ƀ_lAp>v#[Hծy KXn^lv_h0Oj*pF_5i ;*@Zz\N&s۞\/mIkτ Nyt>l9)}XmS6_pㅼp\L2%)؛X#{GIJlGg]iQ +!1 +i Szp❶["*`Bc* %\B2dx\Є[9C8 %ٸ78&BRW̐CW($ ٌQDu_2Bdq A>q:$BLdIgD҄B#so?]\\(Sy2"*|ƙ@*TD!HBTHI@V!@> eM8tN$n2k\U/yČWPZ 2UXNjk`M6Ǫ!`r8L$oC"OqLd,P aB,F{w k>~8$k螚ATxiedēW cJHY4]Z3pufAUDH} CSs@ FJg6pgPM 2K޿kukXɮz0)LNrǰ9:)ʯm]/ p\+zתa-+qU"ǎ֍RdF +68*>֍Fׁqĵ}GXW,V7Wϡ y |RR~# %/[/ 9?Sw7- fEnWG L_FjAB7dsmusdJ(EpNVVw̼񠗣TjץLz73ymeA ,M=Bt +yo:#/_v,6Qd ! 7㘜]b*1 |Ψ֦^FTnzlA5L[{C,<8Es|P\wsZw'0Jah\*ZNZ5όS1[vMA q$ YEetް=P:[,cAgxr6:s=zѫÔ>',]IP8dɇbҔv> +D$npv4YZy57&*PU]o6}<裎&q7i_,+Lr$7h>Pe}ر +0xË%HF1jG;op3 KImLG|Fd[]Pq 2.x5[wCgDF8)*W2(dQޔ3c,!Jc0\#0   ׏ף#*clbb`I43K d&cU"U' ԒP tjфh (¸%Jo-gOJ,5|:R(a;JQZ"9=e:K(GW~ IYXnB+@'ǃ7>D$vۥФC۪rr&x>(N[QͫeG9[9IVʽ^ծ /u:ڮΡ~N:KKh<kzpUmLQsoUM0WLI(l8Bӭ^*u+J\6(2fW|*yofx=ŇH"ВJ0|+F .8$D<%V($0h@Sĥq"AH%[nPxƂB ɏH,AƇBb +-}-6   $,>""CǬi'Q%*q}jPe\ew*y'*DHU*4\W-e/ފ/9Ri>AZ|UyEvgeMZ6kܹ;8pfιП3"g+i<1bۺO[o_+X/#\1wTG`܇ȕ~=cg$RC\WOzoX7[9axߣzƺA7tޱdu-r2cy?{xbɰ|baקּTr,.v\#6B[Ή4k(ge۲i`:`#ŕLwd³W26xuWjdl4 c<x}gu/Cw]zk9> H S9lziW_o6ק"NIsM + t$GRq"} EQE;يMu쭨TX6DbC? +Tpͫ ^mMli@"5-D^oׄ-*:˻QһZC^^2ˆ?QjgD2|_Wx1/!JS#(LwE1%ʻa,mՆLKi5K5¶m(%2@ٖє9BrV_zCDÞ(ta{kcD(y;o2Fv)12Z婿To1GeJ?vLiI(7D*m-l^-+b%$ez̲!JAolw5%|Dih9cUWhNo晙X@:PN/PǺACˋ$5s(^숸}{-))Yyl{b\TC5-8ݑ:iY`;/Xf#$ }y{!ZںLv3THl4dYc#OP]֐zQjs6ڸZ| 3Frt+^Y C oWpPC C`S{q,J߬4 >YL,k\VC7p[42uԚ!=staOOP/GLBpimK?9$%G>uQ,|5L 3K0lY۷o,z-H\8Pk|'䴫0:bU RU嘞<8BOD mZ]yKvE `mLЋ]c̭}t!rߧyh]i jU6y9s!Py2 G#1Û0fy9P;'&%FB+xUױ>2; ?lfgbn[I$Y;D&t|K +ozϘy'H!kOV B(Pi%h0yuмID^y"{DQyku%m u +i)?bҎ=V}0]K^o1F,O[_ZlqgYX刎O=R0az/<6ȳ4ɮL uy2e@Hw 縹l`rA5חV8v!5!*,Enй# q|.!'b2juYb:c +\4TWugTaG)}aXE*+v&S4^Q4ܾUu 8.4|*ɱo0깏BgT\2`4>ƆEFc} 6׉;T(pm:M6 +:vjͲr7 `[nWި G:20.-o~k9:6ff{A+T#H*m3&*wϛ6^ uJy&9zZI ^~lvB|lji:g;kv( + uKKA +"]ˊ *dv=馓qˬ/XԗJQ4U1 +q; {{xJbPuwE~!6,*αwq^qHaJUl34|{^tsI ^p]*> mzw\RB :I"1D\{rɺ"]/fhi;rЈ}k>6fysP&! X %ijY}a&!Df'YN"Wgm_H_ >]AKA +JxnZXP(Hv6 Ng$k-VDŽ%ovQCE>Do/8h&x +QĈJb(ɪ/ƪɆwؐ7؈wјFF 9 J=[f! X+%izY}cf!Df;$qH=86"H__ $q4g-tuOKA )QEϭXP(H:g3$kVPKozZc;U?{a1v'p/`ZI 1⾣:OҲAʲs]We\$nRU>[l惆O 2R\Sg+Km'-vPiի,~] +oБNY!ړK}dWw3=S6#NJ /ncc17aP +Eo`R.gofBLdf=I݂ߜ퐾$%ß>uOkA )1 !gmPC[ +io"j"A ߽'`QO'=c:΅OG# uTzHس=lI>ŵ)㦐>cI|98=oCH'6, \qmHG3 +jl/!F?tdVgF +zV(>oW_W}1P`GN/sc^'ˌ\;~i2)2HirZHwmidS6Y V^w`f +ϒMeJA Ew"ߎ($SթvߥGGa\&\t D7̫D†}mNV:1b(TmG2?^J;K4*ϝc#n*$puÔ|54|{jqT¹$^O>*> mzO\RB-:I"1D\{rɺ*X.Og7҆wX |4fys?vvRV(I1uy%&!DfXqg9=7gm_җ#k[~RMO@WD^HShPBU*B8u^qH>3ͼ}o"Bƞ(;}(pq2'Ї_ H V9, _K)GѣYOLBgSY {.GNp #JwXօi,"px,;ʓP0>2-* ,B^9Ehvd z1SFwc0%|^:Uޛۙoe_KASԣ(yK⟜(h1雭ug{ޜ^<c.jc?K#>.Q~ ЧL$C(= n;$`^88oJ\d'67,)ֻcs-IלذpVR3 QTI(D;ĢzUi觜ߊtNjDҾQ<=F#~%nۛ]ql%6E"V%rE'4rYb3/%N#V7|vjgƚ9; O e]KA EWGMEAEGA٬ U.[B}L87Q +*oW ߞN:1b(TmG2?^J{+,*ϝc+n*4puÌb=5|{^zq\¹$O>*> mzO\RB-:I"1D\{rɺ*\.7ʆwX |4fys?v&Ag+y4w<\b"3y6/ 7k[~]QKBA8)b%FE`BsݥT49|3seX6 +_oC鍪~>ޝ>0 SQo<^(E4}2=֩4>r`Ɨao1^:RlI`bWIjadh纊԰d2|oTU&N"2]N9Z&n8~W?/uOK$A )QE=ϸ:ʈ"QLUz:lue<嗗Ǜ׾"qI'l8ÏY8= +8C/N2C t멭oR$`^d;!M77̩)_??qk2R s.j_%@{ +҄XԛG/޳]ݘ3gЉY!ڕ6Kcdgjy{vmxO-/N؊1+cXsJ0cϔ%-fX}Naf!Lf-ˁ$/Rjl8ko C3t=;7pmN1)NMzʏԢJTB&I±]{6 7H̜gg_}Q28<{KgUꈙ6 ) ?K +Y=(AKA: qe>r3 +/i=hM—7/0_{jIR-[ %cVF+̅vo"cn^iT$)qj-%$%]CtGOfYU>| Qp ̆Y ňKV -3wXR@d>% (q u^ &lP)FiGu.xr6VM[V67xX5Tnp?|?5Ṡ1﹵Qؽ c'N[~zgΏC}xk;OKjXqkfb'>m򝶴G\sv:ĩ rP7J#18qRD[ JJO+z0{R1֭q,2OJc(y-ue_rV5zϱ\jXҒi}n5Lꀬ<a0bIut=iŏu~]w}LYY*'BYW˓u{龇({5ʃɞeFڃY`e7Ō$S *v~xe?kXyo$ivz6i~lpwR߂-DH_?}Oo1s 5K(*R[UJoUwvת׶Ƴwh{=}-2C^ExwL& +&2 +c L, Z!oLAQڵ/lJ`ǰbGh$sdGpi1gc>RC 8 +䃥K#qYsIR@NlcDQ_L}y;UN$)(*@;6(Cc +ւ>Mjk4R@0T:G=U@X4{_o մ!ب ;/,`cIY#hW` #UcJi,a.JSI(]$t0U}S{5ȐnT'O[.fZlx{5B@V>>%Zx6k%Wav7$gRi1wǥ5  (G-i܃W qN`|'+"a)Pw>ڙ~hi8K?FLgo "GGf$I]IŵOOBAo1CF\IZB"*AT )rY gllj*v{Yyo:u `FY;Slb0g>|F->vV}KY NuLOwbx#LX_I4cai NH[[;*$XaL]] +W$eZEVۢQr6 +#%3hU3seoG"pt<{{xV6h|E ^;hm,"bC};3vO9YG͍)>= 㞭~WUX7žYg~;ˎi4~5g_gW){6A0s vQ KYVEjJEę$VO@^9 t,U}{o^AQJ~Sx7'IV6:Gp9|-g: w 4ڍ'E)0RcxdK6hKUze3DT"V伡pM\ThmO[[-Q +h3P +ǡ1HIƀj@mBN@ptl>m7O_"(pepRĉW"P.n:bEIR`jhlB( +8AErð~Խ$^ZVLR}N1{3tAү= 3^ײ|VxOrAr~ȿGΪԵ.a^߅Oo@sZR*4BWYϮfgwց{㏾P=(2X`xn ?JK" 7څ{6R an="KbY~<8*Q_0"8_|yMyS)6$EhG&8}v R"_&5) Z;G}U@s_w U!ة ^XH  .FЮC;ÌT+FYR8էu$vdLV L-VH^Ɔ?TX,dU!@YǑ''ݷ[$V UސQaHZCS;w5_veNwK*``AiZNwTNd*:uh[h9s?Dntmx9#U.rtdFjw܊zp|hI[ޮyAk@mܘ^k;ubRjHK!=x5wYPߋd)N&oyZ|uDƑ(X[}M +L u I·d~|!o+Nz]e!>׊V<Αɢ ϫ]~]<>sf"V!xDz?6{ +%L*v5H>֌*;"a}@j":8Z>6MTۆ֤8QBiy8YMc)d1 Jڙ"7/ +(K +~0L:6kdcӞOf-IWi^yrwwx;g ۭ >d7Qӗ>mY7USȻUsw=Y)r1,~ n&_7mn0z= n^#;*(j#ƚZYDiXr^Pb7-?̎lc")u+afy2$0e#RHكW\WԲ&gc?Xn`ƚIC5M5:/Q +0v6Ut >o=u]0J8#6E/U碈R]A;tPIw +v7ccTԷ3J4nYt_$EX;/|Y^ٲXrw@bOj$ +bR`4ELow[-z;la8KV] §%alyI}ٞ|Sώ$/yk5덓Q?>< @p /pg@W ԽscV%C*{p4­;:0N,;_8#f3 1?,liX[tJh 89HQ8vgpP@nEG ]W &` +&s狇ER0l s11oOghQBb3q\}F{hNR aQ`=NmQꆐ)P-#0,n Ӧ +mc|. o0놵@ 4t؉#/ݦV /i}+W98ݡK+"3{3fIL+Ϫ_۠hB$EvbH Z''ǡ! +.G 0c&FIL1w´ ˚ϱS :u(,r)H[1h;Vp\'6 Β1$t1¿-<7IvBsHN @ղmX >{QCV6OvivN7**ڼi].d JRg4gst˨X}b5鼍?I9ge-zhvHN|~uOo1)ޡ$*?FETTQ )rmfƉ*$%{~WosђNh&maOx=l|Yq ";1ܑE$ #?;S\94[-ݜ㑖N]*$}aL):oIVk["[Eb /%ѝI`=+!t`OQ $kg9r vx?xWG iX [hYhQ-[i* >KgD&a~MSD9>OQMNJTYXhM f'8ZU" o6't,{t% ? xJ]g5W7G Oז.VdG.dEM]NA x!Ihbli\fNWBnǶ_?&}Cʽb*m + x RPKÐLjH52=R]8y{M0|LH#Oק!VbBܲbRNiSnnF +>ESY%F6 Ўns, u-8Dn +Kxw{XOEw`vTPIvb+U⣝󹿱sf/ي_}Mo0 <i]4KtXmݲ``d&KE'A:o_>|Lyї2RAX+-{O0M&Frmt,r^"/ݯhuNAh R`ȖɠC,Ȗ]F1hjbܗ7t|".*,[[-1 +h3P +M-C1HIƀ:ƴъl 6w\hg a j5}_o/lCJaLveRčW"P.n;bEIRPڼ.cQ0(q^[Ax$B7(t+o"+0V?o̢2NjG`G%%[X1{oi0=INgĵN&l̙f{'W +^8 ̸ u+^'V#O[+Ђpi}ˡvﷴBG?Mo1+|*R[UMoiwk1U7,%3~Q LZ`ۃɠJ +PA^+Gx~S + rA:aZ-݆Ol`d_8-.TR`Da=L|yke=FKlYaZDq>;)h 藠ImH d Ǖr ޠ ++uu6~{P6Tk 9IJ"krnYUaJYL8Է"Zv&IӁ`Q Vh% J'ueLQ!@u,Ԟ+M}⦪gZ)AjddH]z}\үS Nwmos~7na/( lhu4kyVc΀VPY8gFlpo|$LrtdF+X̀w>g,{yt_mO0sDtQnYV{X5vlCW߽r[^~7|I-:γ>59 %4AEJ ʡaKO¨Oа϶~g<:1“7w64Z zaN+(cQ]dkMz#bQ>ƳZoٵ^,W2h y +i$GP^YCI8VѮ7>O~'QiWc' +^T`|s6$H[PN?1bC˒$2-&x6:^L,k~Ș-{|+8KsXiV0b*q\ +O 0 ++Y6ϛﴗ#m㹋}z$ӒԽ Nõ\`akNl R4=)Fbsm̴d=#-򱉓\.UEUs~fޭ7b8e4Aahw+kvBOk1)ޡ; n'li +aV;JB3J{%[L6g)B4;X1 3 QV_-XN{cvU,*XS|=;u geH6qLljB8ЖJAA,3ۧ1a^@;A. 1O.$$;ߗQ5ԒbKs:кĒ ĞuΚ@K"Çx㪛33I1LHP^wi -;/E*&í0.{Mb|9iՒgS#L+#Y= 8وsP$-ѽE`0FG:@CT5WŸDoŏ(Mâ7lO{zlEؘ&JOt.5iN:WmXdiMl3 j[04MC!IKȚ06oɑ+N@uGnW}jAuXkCb; +1$&`a4[23LFVC4|U]e4Jx_ϫ +x邢 !޹~|sZT8%l;TXF'XLpE7 ̍S N :5rr9dN$؈IJ 9XGCEG:OQ"HJ,$BtJt_WhuH:g9E8 v:ؘx§oVzjvͫG M#{(&n oᜏUr[GX X:Ł<9uG_7i lw4lI=^aEٽخm@ٝ.߉Ǘ+7en1 z9 nf"ڢ@z ZWHnP݋I>G+CAG!y`VH7]_8\}L(^ S~9ve\$,4"LxHb~u_ut{gZyTIC rIt +>G|V6!d6jYtk@kJGb VB>-fDI'f;]x^NR4lWtQw&\%Bhεc?4s!yUy,mh$6Da2kĝRW)wPEm|:SOL%dUL3jv0횬 5ݽj@zP҄^k'Ml\hC!!BF;kJ޽#P|O 8\&ONj^8}g6!PT;77pҳ{v.ڶSuґ W~V)xCI- 4Bcۓ;Ռn1n}vfp ig KbXi|ISǔ7:ڧ)PklvМXC4 k9/zN 7/ +(%(YIk=+K0SOa8k b3Z[깬>"+I;rD! +e-뵎.gYnV O?pLV<>7~>c*cyqV <@ZuwxjUT}j1ݐ&ib@ +Pd%lSe׻.1Iuo4~Ϳ$P F*/%Oլ8-pg4>|F2 ;#aPi'~p#LXÿI4cnz=k:#MVoߨ#gQW˱K1MK9K͊WZb=+q1SO[otl{ilHo:uQF,%^U1+%0ׇ6KC-ċ݃i<1CIuٰ})g?N}_W<ݤ6~[n/_Tn0 }WpXizv] +vZ`i(2 U$C}ĩӋ,^)AR 3)^9p +^t܉w)<8A 6߹K{-QeYA< (ų.TxVO,׆s;Nc;i6ƴb 80)HkԴ`KpZ ZI4A\ N 2r0R RxHja +K9pPۂ$)GF;!_/ +P ᒑ[n 2w+փ\V02tkm^ظmkW1qVS>8xƤ 򀫀tl $Fzg!JZ.k{9KemF"(~Jzl/iZǭjnjmoQf ̡&JpTxFͶZiLJ9%\D>❬pD%>R.,'m~4U IjZ3i|TK]MkA +C!vn>qi +E #MP8vM.ѱTxVY w~p1(AR#;IYaޥV0f~"H)T< q傥y̜rC0[%XCA'RѝT`#1mt EAT&92)%fpz|ZS4l$Æ]Џ_al4>uK M<pGRV'K3_c&/ldq +[-Or`OgGh.weOO#1 XWZXeG$f5fy];gV(a +̰~rY։OAcؓweS hG 4:۬9&%v}TIE~;FY-Rk*<։HxRKk[1gQMvU(cݹ#15%ȱS0tS4|sq4nj}$I1 ~&s{1PЇԐz|Ioӏ$bg[&卆a4LW*HjsR~ yI >qeży̜rCuX[%XCA'1 jIcRk庶F>,$9ALxݻY|_m6aM](/oqu+gs'a3|Rpbb1/c +~@<*)c`{dLߡɰQ7b`w'G㏦³W7_5MvVMo6WLYH +8ڱ&EHb{(`"J*9Jbt J-Ɏ]'k4oFsK `dNsZha8p + ,ȘgJh,F+#Ac(kh%3 +Y +ljjib[kK eL-8jQH<׫1 + .FBً<_Rls3Fُ;qc̘.U";Zm\!U-]CTK7pQ'өD & \6e7cC [c|~x[];'-vw#ϴfMꌫ׍iΤZ !|z-ʣu?3UÙ&`Whhyd7_Lz ,m#eI:yt=ZSuB9>ZȅB-&koO0SI?ut\XsȾP>9u4&wys{!OsKf0d{hag8v]*,$B" 93:)3-S"AK+oX!(d'pΌ‹U8W̒` +>cmup9\b;pf1ZBVdĪ mI!)^uRpTADЪDfnʼb9wJ2  6R GpctbڜqlAa>~KPѸ/t7Pܹ(Z +tJ +`X7%Lv4)3f>x68팃2{3 [#F<0B8nSvy_JS(ReEަ[Zo`(ф܁5HcMZñoH£}4e.Rܢ (ܴi9L[ZN,(M`@+(N W3`DG{'Ѹzn¨Mk}jJkـ3-zp\BƳ#Pzc2ؘÏvckWf -d4*_Yo_?YxV"BYbNns:z  +\4i P=ӁЂ,ߟJTkJa2]^}q%ð{Ge) j1uHz4qi +EήD#mJ޽z퀛ҹfO3\BE6LhV.>LgY3<:/} xA2Yk;o}Mv.)J;Mf"EYsׇs<ڈzJep\ MkO؎( W5u1%f#]Gў sskG>G +di3}]Vbc}Uxubl 6V4^`Ӓ$c~ޥUXA[%ԭ;.'#ӏ腸Ѿ2izW 鍎O&tTNÓly)LqFVۃI?s*'nHOV6oP&^LKّ=:]Oo1ݪJR"#rwl<M2~>#mF?tIp\A"!-cmO,D/ vSʑN`OAG;Zªj*%'5FБ !p`5$@dhz?_<>-S;7[ + ooQMPT#ޝ+v ѩc\"Fj($H~+q'8bp+Q3֤˻iڏXysn5܇Ͷv,|vK_ͫ U]KBAW*tu"b3]Zw9~8<3.fpOxz,7 xw^LE,DT)tDcES*g*Raθ?G F ([vV=6w.Uc A= i@ra7aC,~.60b(ZXI g]ه|YFJ#*ܸ_mFYp YYb[{nⳠ-n,2?LpQa`-1lHc>F DϳЯyCx}hH\xβFKpUt ~go`cw.H{iX;dg6OSNK4p".mVT@+i6px;ۙ-IQB@G6nح~qz̋S8*& ++ŗho+B6/>0/r!?EE\([R9D0d0NV^&g5*ߥҶλˏނ f K dWyQcq7lat7*{mfo4kZt{1Kʬ> 3p5UP`_<KQkQFЂw'84XR3ֽIm·Jػ(f ØA`U=L۱uRMoA )W )j#CHhΌl/U,M?<yUm2Zr:dM /aȌE0 A97;'P!f>lsr_rH0Vp~,qXrK$s<#KA{'d6xGO 9BVZ ht`\PbAagp_=-fylU \ Cj8 SAx%iZ q@J#<~Umd$%7Gcqy`bjtD3%ff(Kgef GХ ǢNM.LwL S{z5n{A/7hJ`ޱP]YbFV[UVϸ@'³sF(ڢ4V Za\wVNfT VNKw䯉b5[z'Ӏ~ }[3iwP$wk$^{Nwwz[TKo7ﯘ*+H1z]N\AsglVffVP>zH }ߪ uc2ZeW!If81|* CnJP)p9<fo"/ڑYN,沈J&tK|Q,FYx5\q' +]Uf2/86#!tݿ.FJJT1 vU&*p 3)@2pEJ> ۯ+/nvJЪ^#mΔܱ:}_Hl,E4"]*fXui1r<"Xްxp^W(A^7URGCZK<44Mڐ~FvG*fgXw + #;%4i=8S45uڜzLdG_A+?ӷd2#hmDܵT_:49?<հpIAnԉO+XVMn0k CBiTRU$0l;=)$YA+&BmHLS[;R+OD|њy4y+<C8 @{nuk??{'c{c`BןG_}Qn0+搃l1zuH M"I+A/(K$卻3\ᐑS+[~qFd6N0cr * ϰ9 Uuū炑>{CMZuF h'+apKy,B7X‘u"dְW7!wÈ +5S:ڪ´ʑ^ +NG)M:]T'9'9$}HmOk1)!Ibz6Rz +Yl$*KbF&|"I]4iqlBƓD89^lZa)zNH2b{Fs41={SGOKV,H%͎dA!kEQ-yw2\l(.<2F׻m˖2YaE.5b&vʳ*І5a[yUO= >7q51 +MX$h{ $-)3Όwi^핡Π)c&2<9 :٬rJ_Bm%ɢ.{RG?΃)KKz:!RVn6W̡EZ'EC(41Sb4S3ܵ^ EjkK<>μy(nشX;ָ^WpkB{wUQO_ +`S  TzJ{h6`%FxVccW:x>Cy*@m@ [OC^N21XBcBM{ԬDLHWEѡ2OA:ˠ@Q(`| `Ewؒ_th!$Ye]j/`5 E_Շ?m>ۻ +y;ų8e)&(^'cpp^2+ 26)CgԖ%m U60.$ܱ}g Vh'·@_3]w)Г][AZ9t9[C73R`.c*,\٣  + `U4OU >8ZY5c﬿PBMc,SGFA90)ō{t#+ڀ Y-CQ7e)^yL]zQѩ RtN6DG^uō`u[Q1ϜkxӋho1@ȷ +oϲ +tp_<|!`c΀q0#$[;TCTRF϶Xo~Gc4$}&1g#̋/L~V@-c">:Ġ ߢCA>')\i!ޔ(?ufvDT ^zFeQSC3 Fao-B" ]3Y|s@rN%;duʻ&J7M@M5o7avgwCE^NTrQvvNOs;sxZ1v!_RJ>!f`j!TXҡ g0U 洃Ɣ`sAE[Q?yI͞cSckmJx_0\,jJzt;&ӶZYwߘ֝,P{hhZsJ%0*@dGqFl`SSX NU? xZ-tt~TY^^]s",Gŋ*W_MoYjc|U3MѲmRn0+ /^u]GM-"I#A/(Qy̼mGW:$Jr/}6J].UFi +p3Rh[նd2'o09`,t4"T8 +3W:NS05պfQK)01$,K(I}|Kd8n}i!#TjHFUNSk:&0v1 )oMbyLKo"Ϛ]F٦|zc^K_kُtw\(M Ϙcp CC%]ZH+($2e Bf4@(_ +MC({xry512hx +B60WJ2@DuI*!tE乸"2tRe(0:4^_ߧ21 rm}sL<\%LꏔZio?Pr]#l3z;qLx%1{[ +~{# I8N3D*NRpΕ&Ό_=`4xD{ѰS"gr6cR;'Jc6#2B] w!lHN\X. Lϳi<Րt_Z[tU/n-x9|隢;<)A3*Lu6nc KU 6j0?o^I񠠥6dșj @t/n GN%w$B{O*=ZxS;Z4tV2Wi%*M!5e3y$)*1)Lk@J$-puLFjlfh4׻Pg>J׻^Vnjn P, gb8Uk"j4ƉD{5. ,0P]sE#^ sfI&0%]R'iEDn%K!"꽵(5oD2SƴM"?|x$PWS*>8>ԣ(Kҳ]75zb,3}nc*7&,?~4jsXc]Y4Qt]%RV +FDFY͖=pR!t)/%DrYO*Է̉k% +Y%-uy2ۉxOoL8Rϳ +^%}v *y5B@0JYlfѢkl?mڎɨzv^;\ Nz~\iIydվG@Ёlʔkr9zG7Q_mu%vE2gn^ݎw㐧nv۶7۶ubsZ$6͝+ڢ3!\s}XB6 CGcU7n;^0mebmy]Xه"`oNY[Iu8֦K]9kٕۙȱbYW\~WջՓfz䓈5Iuq=7*7Cχ/O';1vN(Ok|n? g?vVMo0 Wp@Enצe-V(v(P2 %MC KvK#H9Ov,>$8cxșqf@mAfp-XIl[iZ |DDm -}rk|&2" ܴ*W(GgD@=Vjl;k( +D&uA,b#1[fuurP1 ;b e):F"PO#A +4PXUdE鎮X,?3Jb~:yQNa,|%bXV="c8e댔f[bhu<&]רȉyסWU:b_HE،*ȫr|Ms3 +Y)Sڸ2pdQ*B6powmhTI{\Ý,CX~0*p&X#rm}"eZ'g,A̓CmIIy"SG$C. іZ[$i>K6JOptP)Gl溾?4C53ToRe uB:qq+OL6[CR'Lj-o°VXKg0sFl%a=݃vNΠ>Vi7'V:v.V5>۳utt_nqG+KAO0OCRؽRV HX ΄X8eOZ*rHۤJln͌sÕ9I-<+G{6J. x.U@4A8StQL[yZ2RO-y|s>i<Ĕ"& ܸґuְW/%ܺGVL (SX_ V 4@X(Z6y],4ƥ`,E@§X*.`k/ 曖/#* +NHS)5%I"ֆGXGI"*VUDWTဉVM#7 1m`BLyL8 Q4mЭ`?/Շi k%w]HkZr2{T=a׸uQ4.l)\ `O-ğmzkNN<\Yx +V/(X?ڬ-oث%+D{r,,mGQ6.m~6NUPMO[A ﯘ[@6mQT/겻Q5Oxx񡆊}"ᩚDomWY/!*QQI }|02$nag̸IbAj}:<I-RgXpUCR7"(w%`Et/,0!%zc9+#Y,51)9vzpV,aK.-`ͼA<×`ezb䮬Ə̝-ZęҲd;[aqoJ1~Ⱦ†K.R 'Z ?@٠\\bc|L_ݫT]o0 |^&muX}a֢Pd:HH'Av6L/㑢|q +J-<&^I~_i:FFpW(\iEg9|-" ~y.µ7o0?3VY1,q%0KpA+Z1A 5ժdIz!/yZI4LnFf N ]x RUa\ չ08OAl_yd WFQI!3p>]B5d5މL|Rk/v ӸA5G# +#-: V0‰%5~)ٕ< *'Թ5K[%q@@ #Rv}S:l[T;{Ӱ} PIVe*dN-YpII-qpH!J&5wB JK/!1W.i3H[Bt Wp(̥0 +>8 lsZaOg@MO̍Bf_SE(|+)-{E}ǿv\#wn+I<&Ww%-+NxқV>if?׼\kc^nMnsħN +EX!ȬxfWK)7imOk1)Ibz@cjg-QYYP݋vhuͼ==-?$бVݻ颚*:A< eEh݁o+&vVQ)>)⬂%rxu $( Kcl<!(t01hv1sPa^݋;A.1I] HIqCs}s_Pc08srGZKa]"ϫ@{Dh)?ħGUeJ$<?`jJ2 eL-L"̲ϔrlZxj*W +6R:(8Ddg:s/uVo9X=K]U4 I6,IU`D֪3JnbͻRNl79PCQW~Qn1+怴I$*Ph".M޷Y6Mvz<3͌={ۈL$lm4;|omBc&D͂ඵOt+l`dxϞiXf=]ViMQX21fi\6R:!aUi0S;k'MёN'K~uy[V :iE5VZHW>̆`B|^Qn[aѿTEyb62U8nZpF r<+r֠t;[C<ě;r|*7LvI2=|"dYi/zQ;B0Z|ao4C3;c +0@XLu\On&d=QgXC>_Ok1Sd$1֎84$CYZY7w1%褝{N?CȀqЊ|H6Jx4 Ao+L%XF`**pv+Hb--|È47]xpPFc@ih-]I^7( an],V$J2ԒԲZs“A!(W +ᡒ=5XWb5d"2#X8[-{`aӽO)RpD1ZAJeX!*Ά*Cs]κ\S{|t)Psf c'ʗJRk$;w}MF;/-qK>tat +җ +;SwCC"<8LGȧߎ\K}Vi3ޙ/X[O9~ϯ8QehVIKSHز=k{h_s\QyqC#=% \?ŨwnvaQ H b]^,DO.# ^Ї#>2 +> q<))pqB <@p-}TB c 5r@BT BXS||'e@T0#y% "]pB""DZD:DJ4|/.g'gHx`̂@$\֌98s5rj+ȍ)K"]t +\#o'NW3cuRph 'b2`UQvwwÚ4Wl". 8f;tM4N\>TDljn0 +\gTC%*((4%MS͊)2 ^mJZ9N `6Edh&&`ኇOzjaȐg6Sñ :5\{껠6$WyScHm C/MB_"C(| G4t s=y2+CDG*M? Z YL| GŃ:ɣ7?:Dӎl. #ʅ* +DJTB|ؽµoz_PfzK.zŶSybilc0flK-#QM_JL+X sŻ靑2~Ll#kz•&ah>ք2ֻssv!$ +vkԬ?0MeX޽Ai*ZnUh6K6ͫ}ބep.]*l{\-oTjQ=͢jo yYi#H3'S[}k lS^ܤa=&;1UE65+8k<o LaK [s)5)JR,KefҔ9Zu-68?Qi̓iU"*\˛Ẉ[-\U]KBAW)tu"b3]Zw9~8<3y(&-Ӎ(z,$w AЄHbY,|DT9lȉm"Vr2k`Ik+l+Ę%_(Hjúr>gzBo%!dZ 9 P"Y!O{ojuN[ + .]y-;$%)xfߛړP0g!Em1n {m*c CP$+Xf7[2Z | * I&/MXL@P흐YFĝ'O 5B}TOhk4:F0(1ލ XT6iy_泇ǧ &(^ KARx4!mTFXԊqENNje*fXDZ7Mj`A' 3k?pkEoJƶI82k%و8;_(g$ߎ;BKk E-y Hu'::"zrҤ4?vrj:7(`Mٓ<5g+.2)$(A?xIȸU^m8n`}]UPNA W[@6Dբ­*g֛1ބ߫&~~K +j]a4tAцBb-31e'a~kIH3WC})yIjgLU +A$,{ˢ{Y`c?Wv 2BjBN(Iam]5 [R4AkqmVk3|n]5k!xHbԹ^?By ;N,99I?y7GoƩQ|$"׻>j +ǔG'A"O^~UK#\\b{C?T]kH}ׯ8 -MMkR(iHv]J* a'2#և]Ϝ{=]ikf(܋&{!;ONJX& ͌*pW -DJ#!>IT3`FzrZ3B .JW)d3b2# \E(|]%Y(0'C,a#hެoC(Ua,ra[.ʱ wq]̍z'a.36BOa{*I] 4߼MT<2 oׇNV?Nը`4ґAeMtvZҞ][lA0=o;3G͇maY )>]qy2*}w!ȻcT0 +陸wfcD#[ߗeI.,5f]# +p 5ilMB}V^Ϝt/> {ʡȳxn9Z'dz=zXn(^tOG7Pb! 1PFv0xee{Hc'b܈Xq߃3!\ /yU{#g}ΊHh[I}'DQ],LxYtڛgp +Bx-qv1囬?P\vx2yg^'}Ro0_q<@D:(˨VG$dKbͱCHiG'|2p <ϣ$x)u\Kbp9=}@ +&T"O!&QOrD4)@PaS׭"'c*Ph`( T6>!r`2fYwXL)v$Ϋv%2lϕG&҆%e,yZ?26hUzȅB^@"xca=r,͏TN@+ !;J@6T +!hCzXv֡VNb;{7c-(pw$WS2.F{N )by0<j*nFUB i^S= _D۸Ec 4;Z8 +>>GJ@ZD3 +1Xք5>\x )q)|lJ'Iw/"- +d+$s.ܣy;2/-2^D5"3 ^ + Ԟam7z-\27:Uh-<©Q*D(sxHkrFgGP}u 7,=gXJ"|]¢ ^,|q@ +F۴jq(d6`LC>>nei;9޸*4yzU ֵDq2%w%5%g#]w7o`Ɠ5R¯|2L(,&PMo1xvWmW҆UTcmyf"ٴycn>Fђv*Qɒ"jZL5~#XFTI: J]a[]֊*/S h"(| $%$ ?1w=`}V ёb~Y~{Xf%+FkE-V $'MС=U^m҄{]x62- +3~4aUm +5;8=|2)!\hgGhZ~F{Xt,R F|.r &QpV1qY}:w&HտmHioKz/e޽rQ\L +{2nmTZ4F͐%JI7%)SW[o6~8RX2d{(4K$E F:R$Gd !y.^7]h1`bVTk_dxWp k. Zmx>T6|S$Y +/Dx),Lzf}O:bz]a£oM5Ћ|QV@Mq _.g5d]L2pڳh-`y몼iy6w/t4e5$h+bmeh\r O8'&I-WdX&pEQ; au %Hthhm*uSce8>ly}?=lXдDRd~OU+L~r:I + +-6/ n`SɴZZ6q+ Rޞ~Hcq*qT)ӧ?d>"< ='(w|lDkTۣuĞn>UPn1 +v`$vA6sr#!$8F/v0OPⲆ}"ቚDomWY?M!*QQI ]/|0*$>OE2kbI~>mboH-RwY=pUCRBPnK6ފ":EY1wEb3Ĥqzxj f [RQ߽6Z g.3k%ϸ $e=^d\z3 eƯ6w'R8c)9R~Gl::~cc(a% tE<78@5cz Fg>oSo0xJDAuVCjX)2 y±'CvXClߧ'_zQŘaҒc}h~'< @bWCIk%8eZng_+d 0Tlfq\rwa X#07n|yqA;+LZF1HPƀ ilRBvTaM&緑jLJ%Qr +-aCRլˏU4Cx їQXAhB +Uق+w:pV CO>"CԈgiJjsTl860_/ ,ZKz&=J\p]]Of0:^huo׳(c~aLa9(5KRw*k|Gi2 d7,*5B>E?=h{>^~~VujokDdUo8~_1ZսB˶GYiTGIqbձ#{EOvP7}3~͒ b -iC ݟ40DX ,d<'\g'# A#x0 +/;pˌm{lk2K)x p%LsbZۜeo%\J[ +"&e$B&Y/~X"p9V#pWoR 3KAn]]}G8Ngomf]Jfc0cq_D4͉mei.+&$O0oA%(:M"$Dpf03V;wR9̈#ܛA3Η:zCaotHw> +O]|+ȽqݱdrNa`hXAX&vv\ʨT}f>EG _Ͱ5eu,N}<"(41Q^shha.QH] 3$ϔώ7<5Ӟh\iqZ8e"_@OY]OrW̍)[}BAEXO9cCޣd@lvB-Mʘ' /J GTEWBEt&_ˮ:cj㦎Ϊ [=L;JVo6~_qX +dyCɌ dA%Z:K\iRQv5@$w[ww?x.K31LOFȼ}4=&ى'r5 cڀZCʷxA1W^$5G\k`3jCpɴīU~,$|5\Rm U&:1DJWQ7JIֹ5٭JBrd@F["v~B1S cq͓un̓l!eV- F&KF630uò!\o2bY)_ѥ-b9|9W'H0"XfrReQq/L[&ˍmneoA8Vyul)X&v+ >LZv^_#%<2~`ס0&Mǡ +**:BzkgS"BgM[{yao/n'L[I qU7Sg8&B0=o@5ݪ~໇ҕݍҋ=tPhr-ۢ߼eVbdkR+ն_l{HUh*i@B1:#t +|R]ʥ<~ G\\Y.Fp +GTN3] (=/&Xkr`?ncJ#RF g,ί]\EL}40ju%UO^3.ĦysVSU&w~?mxə\* ADTzqhEu-^ʨ>j?wNe3 gS4/s~ +ci]G} ߧ^wT] S0L-EO$3.ԙҐRs6A}s~l]\;һÒZoUMo6WL+ʦc7EE],[D)R xb{AK}y~ +b5j=ddr*-@ڂ璿|Zwep%@7ik +V9<(7jHÕ9&U r|XM4ؒ`g$ [+\jAh^9}Owo̖ha +nZ,*`m 5oTSűDbEFFD}"$i%C/֋I;Mz4 jtT{Ok֪PԮȟz_IӍc%DUuOo1IJP +T T8&٬k[3M#^D +yoJ[P$ e4]sRFHM%2)1n|r_5 ;RAfq]֗܉g\+_D<%GfuznW1?pbfH^j2>'T+aqP$]M|_ [O)ҏ:K:Iq2zUMSMaX<9p2MCƼX- 9ٽ}TMO@ẂP@DH!UHhǫwШW>%3o{7HKF[GWw0h~I,՞_*jR1$!:ʵIA "GSuVNw=+0NiG%zdS}/{I&xtD_4FôŏMhNa`~^)!qбB9x[~&+I~šmF]@E'O$+Z9&-ix9ة}UҽeVmo6_qVrfi2/ +m4À$$Fyg iٖ%CIu**HQ*a1vlI/*tӏ$:F09H!JXCbf`eV#-;8VC#Gᘄ_F 'n**4BoB fK5Kc V +[+R0=JpOwyvC s %ޙJiUG%JHo]$jui陁V,&scyI +,J6v1"spSqfRtftF9 p$,}~Q,+^[޾7QbVGƤ헿H8&wcƝJQ3eyiP92\ +43>-|ӄzEݔ?I 1_5q r1ZI }snC!7w֑ѳp +'?q`?qұl%y*;if¿@#̯"lr^8v\Z{OZ6Z.ly:af,)4Ϳ?iN:y=j1 q7,66@)ѬE=݆w3T7ibJs}xv.0r&uOta!>+cB&u>Mx97jڸRfA"7/j'rF>ә#XƢSp.r)t^};xG-b [ 8',oi$2ʻNDVC$ܧwiuԸȡLӼO5DC槵c8 C9mq*Rsv/ CB"%KED(ksP+e._mO1 ߧ&>[ +[mBBi׋KM|)PZ!N,9] E.W!}ꍓQ?>A4"p3 nsS랼Z ]ك/ |,3'8p.0p%z80qC4 L +j^/RkzحDCd5p!+" +\0Afa +k9p(l%i[(1@rB"_ۇDIRR@r^I" - +a+[p 4L0o0a.d<؝W+GYm'k%_2x|RrJz "PV~NdjE&{>.-YxfGlʑ֊e-_X2\rsG.yU| 3QM.񎶩{v+.BOʚHZRXh U1 [r*HFҊ8s(\ɸ ®b)w(CK.t#!(3,*;ZmbĶGX]@-%i#j\RlT( mj&pL:Z\&}5[qZOCW =YXi减!<ՌeZ7T ^`7''fi/{czNTk0_qCڐn#nl1 +9U$MwNF!5CՋuY_P4"`N{^{(A/RAx\wJ/4:zrY`a00'U~ZefX b-,`.+LakrU.`дƀܺG-!h۸7(aqx?nW?gWQ*5J0A kXiVyrm]˃̊Np~) s{iQyX'湺gkb\2 +3AvUp+[F7գD6A}[-w)t#ѣdL>pAui6u(eJTxŞZ\2~H|VA2ZSړ9rW)St +(W?=>~[F8=56548X\:ʏ?Pc{$; v\V#zVMo6WL;nNf7M mjlRidIbH+PCy%rޛP?@q.CHٕ|4;)fYhTO%kt!m׷_((΂ohu-H53 AlVLS^ ?]х=Iyfhpt>E%J`ATqQقc==Wc84 =I RRÁ1AUPU;E"M‰É &0FΕN0Z0GD_Rc5|ÝJuZ}R6U l< .BWV8iQ'ec満3dnD8R)rHQ7od#/'<n։P h)2ΪDuviZ\,'F'Ƙh;-Vd +a<]J cA;id +ZQe}%5@pF U{TmOo1)ĂHho)JUBv׊ TGJx,O(H[ŔGae#@q~0 CMDi,D_b]=fhw>T |dGd{&bGy[|'ڪ(F9|x䃥drwfۈx +RZKzr[EqC;uuw>bR+AE&vYT`7 ̩Š4a]+^_LL[#\G:v삥9d0+Nc뇄Oj`WBU 6:ZQ6NnlwQђwӝ}pBK-n歎م^ަ~tl?{߫XNv3Hy;4?3x?o,x7zUW6N:c1΍_WU[oJ~$ۈ@HiҞUOEXXU۫ ;P)3|7W_]\AQdKf.qc3̀"ڂ!g;P!>JUj-D4@Em \-&iOS\Nb,#CLkQB8z j8+i~l9΁v=3 0I]ˤH Ž}xλ9'RfXžOH)i,@Exȉ?̃[b;+(% yenX,~2J] }yPNpF`?5 +f1숆=|Ui#aNm>2}$T0> N0V;e`W2'<=uШXkTa&'_,+,,T^tY9hSϻhcsdFJqz9K?шrb8Xo̤FBsУ5#% ,_j;Vp}T|2P*Nғ͵܃wW1 +Iqk b t" 㳁FJ3QMȸd ׫*5\_C[ +4.:Pr>Lvc0|s}Iʑ*{4Z0V4l%K'CC//Lh>Ʌ›"'Rs%e;pf|WCQX V;cdQ0lܵwHWS.{i?y.y&y~~?!U/ܗ3߽M kyi6X\ gCRh1mE=Fç9=AO1xT1!鶳tBiv1w+xo{,3 d6%mrw3Ha5{$ +=ͼs1“aOY +f:Wzl[tPƬ\A2W\΂/1CnWhφB!pc>hPI6NU*A(`Ms<o<M0kg?pveکQEPNB1+xT`$Cģ)}6}㿛=LoϨ+4(*]Lr81ssz.9 [Qk42[W w =LDmO󨢛 lhkxrZ̲ϔr+Uxhk uܟ{(:*xӱ=-͢;SoG[Pq +GV—Ԉ#T"M*:[Y1CN$4m`{ +jg{jW*MЉ2}Qo1WLH k%*jUEϷY5^P^Py5̎?6&mS/ +-K 5 |l@cj=]#;'/b+v4XP106 *7| i Mhr`\yx7@"ach}-3+&lUDmi!|5APyX8&':۫kzT#S.p޿wǥmF3&`ɁŁI63F* |&;0A$( &`5H2V(9^HM7/4#M!GN1P2p' zO IVHBs6*2l49py}v'^LunWq7$5/ J]`v3J1G퐾ǿn +hM#A%:VkZh0=EB)}d&YsX{|8 $LpU;Y~3IXFQ{mp!煨#1]{$u)ѵ7+]RGˡ7 ]cT:eW=m^Fk)v"gE) Uf>' +eaT]k0}Iil`cڗєױ- :%!ĎsΕ|]'")3[2Ӓk0. 9b" c"68@(5b<F"L,\1#q^+fI0 0GW.tQ+IFrRV Jk ΣvuOo1."A?EZUHɍkű,5HZ5⦅G7Idf3(DyAOel'Y7X4nGW +bӁݶTg/^'bi4]W&}Zgq:+bD1y*B`abvV".2!lZaE흡 .l"?kq1 2^nE(`aӌ0EbBxf\ ]{gah?k좳'DrrZ0>)Fjѡ3XfVEu9ْmU!R7i߹%N/WSY0~²!u]tBX)>N^ C⏄l,?UULw3O;.6/I |$` rہNG.R\vN;2;v}Diֵ*ZIIr_)eUj'تN3H+zRMːޑAx ޔ6X +Jx  +ܸ֡uc!SI-{K-B9LZI4AR'XY3Qx}}\>ob$[*86D:Iʌ;![Ak-mde{q{.#deR v _;I5>10:^?2J$n5)HWԪ Plv^=$wL r$w%[b[G.Q1#kYhxP\9.x>żAi>S@ȁ 0k$g=5ͯFVӓć$uPUrAb8Z2AQƗ3PIُ%bp_`gUuF1F'Kxe , E=ipſ4q.HV>h~~Un1~:pB"%DO=׶{vY;$OU]^“K+7\[1&BTˊ2!=]̊Rs=s&|H6"Vray_x>ǖ+mGPDM=\q8wJoe!<Y%&BG:s6w_wj.*V&Ei7_;u +6YMM9>2RuRtfT]nI'mZ<[qQr-}MEwxUﯰ/ckO~~2RMAϯx$,kĀMLkOWS].$!WRа <ɦw'/M|Va/h}`DjmW 7k7OVXFۆ_k)=X]K +<AhD;QXhN#:x13|lE{2/)0e}X߯>=F:2( v:>ˠ9FWzΉ#]˷2EUhX7Z W٬a-QDh$ZgA#Z +BϠL;t@Q1/g!$OQEuA!aך[Q.XAvR03&4C#$ +~_:O*?_ƈy(~^>8R8kf=-Ep\^mrvӑpڸڊoTMo0 WPv5niXݰZI ^HNE2G=xj 2Zd߾)i1 Kc CP$pט53HSdF%'*$ ^. +'Džb1Glb * E+xWHlWht`JnbM}M?_T4J`*]-`cٷ3VAiFϓ",k7[\kL[ w,2RDgjY:](G޾bF(-9X*45&#w[ƴ:yƱ: as訶U{qKu\N,Jop8ܔ#X{S6ZGCA-ab'!:2NI+s6!TUD_}(U4t^czmSMo0 W!v5in +l]t,ӱPEH:Y0Rb'٪L=җW P0g!Uyf X q)Ƈ-e#Cj{CKE{\| ,QaC1@{'dV<'nwht`\ix7`Q1&?=.b$L%Q ]/`cٷ:S+4S$GYrt b02N]t7[\V1/F?̒6߂b8.C^+k^\h%2/Z ™i 5U{BPaAG,.Gn=x΢Yߴ{drܯ/jr?i8kϋ ۣҒO*:Dzn5ᖝ_*?mobkoc[VȬn!aO9eWj 7XַQN@6I=* S(䶀#vEV /Is%vp!C26Ct`| 6xeZvXvo6U={B;|5JCthaE~u:(@I֤ hj^BrhyyӢDc>6߇zGfY>lDYYgD]h1C5JE .Wa(Tȴ2p6v3˞ gSCB얎,T= ^;Upjb(}NAŖhQSojM^|Sߓ֍qAQOpco`{Q;Qۉ%_3 ӣ'TTA~țIT}dU z&qnc /j+amhҀA^`BPc_D9ɏT32MF +^k^u^/(4 iL`bT|IE;=0,\-5Sѧ]$8 lj>C. nG&_/a!WʧX|_̲?EA;n0z9` N^kuk$$AS+-M˕ Ȼ%ϡ(o~]Mޅ:$m +-Koq6*2RX}mt%>٬k@xώ*X"&VUI!hpG 1&f䃥B;ajso=CjBX }LO5\$Wy(1 ,Hڵ>.7nU;JS,*3RC7 ڗȣ̩ Š4VFY̚[3f[bV :szn…F~MRAlVhT)˥. +7Z}^0<<]t.ؗӎ/N)J|Iv?]}MrD  Ŭ';/{Ot9]̽zÿx-=lgk=1}SKo0 Wp@P#ݵntm XN](2 S$kĮktE{(!Ca옔 JtItkJ#( Mx&Җ+R! \AxN GpSX Bs\ ˢD[j L&5ؒ ǖ #Jq悕5GPjaprv6Ƹ K Sn˅,޼Iir/2b~߼HE j+HnHpER Zh#i$¯p!dK+/':>T+ْM&TIڤ&^?OUc3XXP3}ԟ +Y@z)qԧ.aMv. "bס޳]!sHKm'7YcZX3t _~8T !exN*υv*0 z]x[0VCW{\3]YЇXG ܳEgv-DjҼÿX9˯ߥVn7}W95},ՉkAZװ%AqG)rJ{AbM H\ϜٯiB\2Y#}iK8AAXkM?K\toDXxB$zBc ΘQx*p1%Y\cΨ<8Ou* SpjC+m&LJw'-GEB0+@*VfyLl,A$(MIg#p) #&|<"(#3Pj?-,nh%#׿青EhηRĊW4b-и DjĖY1u%fe%3]u}O7m\vM/@e{jIuU(J +^ȵ"k2n/> 꾏UZ KZ@+e/5s#ts Bu:8^y:Jxy.b.;zùMdbBbtcjLQҕ9yT Yjc1ü&Z4vvr)ٕfiPjN1#0,Bh|T&eV0j1`fj4W+F832<ώTK:3HvcRM5Qҝ.-3ΑO ץq|!>5ܧ$Q. VW:S !FGthiPqQ2:r SHG'L7ѺN'}V&3\V0.?o)r=h6KP3$+T>a|@"8r9m/w$1J󟺦;^|KO0~r9fS J:eu9qX򏌔L)KS!Jo2)?84^]oXgeCdciA eQYOV>uZTŎQ]lug²kp¼3VQ`Mһӫyh/]V؟YozQJ׬e1#܋&kWÏNؑ?y52ZPz:2b^9Ój?V[k#7~8g3c}X +q:l mRKȚc, Gg옒^4mw>$N Bat?d+VVJ#( /2;Ic@+ߴ0/HJe}p)+a3Hpʅ$N&!I-S䊄cU5*Jqʬ,m+khap=>xPYc p)vc`߼)Ii勑t_bA9Cd6e K[2بQcRȸ`}g!/"&HUZx4>"liUć|%&b=92D~C 1λ2.% G=*YiSe.Od(aawg#/}QwPUݛR1tQP#k[=ȭ7\QpiWr<={btS;@9w\_gGg!)"´{-t9 WUapdok0WCvHmY֮euc/sϱ"9Yߋkل^Ow PȔEaCx&r`Z1DPw\p`i2{vW/b0Gv\iQ :H1 ,CKՠ6NSG[[fV), +wZaO%ũ|Sa}Cø , c<&؁hgrņK',Ȭ>Vp^j2W(6X:!}w7+1}iHO+=G}PMo1x( J5RTEJ^*!eG55^*TɇFd* +[].RWѰ_[Nh8!Q/-oNx*J[x'gOM4[滚7xI3u$0me"aWeAR9طvl'&($†i{=?=̿γաF5 5cز\>N,TyTx%FÏ:Is$ C6F.9j]Τ(1J4G|\nآͰXJgjwW}x~\vopQ+ҋr7,+w_6;K9IRh}iJ7WɾQMk1ﯘCƎv\iB%T0+*KIkcJ{RN{3o捦|HTZe{ +wI1[jm: p5^q(el ?cT* Q /c77VA:Y8tByPOh%@жvQ;;7$aiy_iT^,6"b'HaceIN+ +6F4"\\o$*`<`[NJ4)5=뭈qB_ԡ}2ZnL`Άȭ噁RdhMd4iNm_3fbnJEH+$|g'uhֈ`eOņjw⠝-ttb2LǠ % gn>N+~Un8+SK`u5lP7KFQ$f(&,re/ˋ(ͼy3C+( LٓpelPA18Ali5bnRC*3@g8dr8 + ι]tC4 ƓZV7[_" #{@k%02ʚ186 +ݟW7Ka+r;.a| >g[D6o%%FO4I*q)iϵ#έaOB?M3|Z5Jo IF#aXv6# lCՂ/zdMXii%mx] lFui3PH0Pq>`nu ^[=Wl/Kk5 !V+yE%yP,~ZxX:EmҔ4 +YB([H\a7}哙krV蛬Y1ppzʋNx|b6ׇ)ʦ&Vs>%}n0 z +Z.Y֮h m@tDDN }A;TIh#hNX$2*SYkb!@ Q'ot!@aJO~I&:y[75NGf!;`]CtALhJHBM=g;2|F ?P3–p=,TgLiv$$&`B}| GmVYMRܗ7 yi#>D'rس;fJ)432=v? + h32\Dkd!ߍ @u/ExBJl(]"oBqJ˫7\\=__%:j^H"*р{RDJ܆^5kZl4,P?9Mfаp 3$:UK#̅%8p~ w[4Ԛ_SȯIVg(hr[Ѕfw.*tOȗaƭ3tuBVp`ru=/MN`IƟ*դvA*OKٕwX1Zz/J$|GOOl$^f|ͱcj&-$TbVQSp*:~H5K{xޢ?Vmo6_q(SI3*(7mָ!0gQnnd!Q"(P; +Le nKFks*b)F1|q_Wַ֔K ѐ1 ^)䵨E"kS8PKB>D݋/"QTk3h7;w^@_>oWkޏ'?*$cAokMGqr-_ɿBdDkڐ\1-8;QMY+s\XTlG Bze4 X+=~R^0jɆ$1lJfsx: zS2z辧'[S;aktEЗ 4&S*>r7] ;6nc Ć.b:ݏ)|bjр_u-Plj6)<dσjø<7BeF ͓`Yn8M-IP<7gm{4YrDIr;BP9NK?6cmL-qx芓6dl71Lp 9۶vqO./흍3*I.O Nky:nmBܢ܏kܙ錓9/! -5#~?I2Y?$97ao! (rzLOͱX'&o]ZiR'p} ?6}t[ڿ7|ߤ3~mQw59F'M5эIWwFVQo6~ׯ.,1dxh#Zua):L&5c )ʖd)mEǻ;^ϒ ")Sjeǿ.Nᯄ4Ĕ")2YBw2)zN |J 27*)7bp9*ҥ&K2YLD0VJ{H&A4n)qD,՚bYL#ln߇a>\`&aLCDl$`lZ#p!Qg#r2rH gˏ>>-?MLJ˦rЭ||']80 XE5ԴRaC3EąG Q,&'''k,?z 2Ki1Yޥ{MS.bRcfhei wr1C+J6 OOfmխ8Sa'=:p%Q9-]U΍T#UJPr =GOji~> zD]0hFO^V\62_:a<.&WQ~t7h ^TTx~V4Add KvS5$a-7Ŋvq͖-ܹHԃ.vKAe_3 =|iΏNAԇq߬iMw{3]sRBV(Z(Ź e ;vg2\x7*7V@rV +Lύ"FW*ډL"Gx[˖]u1\-ߓCb퉺J5',M1q@rvj-ytzv'Gs¥IE&Slo>OHggeri'ןBN(OiZV\Q~&0K(8R^ +<8AS)Hu/ek-ݬ8 Z ,QMס+TeE2P6vV6U= kp~2aڶs? &lU@aE9,%$]ELP&<߻eItdžAh^ȤoGs˟TLD~5,7 q([۰wvCVث<7JtM㱝ƻ IzmXj k-i_3To_N{qYvyeWh=;\ =ame5ls 8ώSXڢM_3>J!>GgCorfD˹m= =*o0PyR r2$>3bpǃNώIr$'cvi}—?TMo@W!P(i#*FR4;EU{ҠD!>7=}LQFouI'YAV`n +aץ<E19|p)l+hpSE"ST 0+Y% Z+lSX?DBtBP[[/L/`Z +V.*j /bC`B9r?$hK}I"02W[*bv-ɫ °F HT_$"G5*dM9k`^y<[(WF;npVLz G6g;d)}~Mkkyɏ!!rAF)ARw4؀v駪 fV#gQ=z28`A-Kޚ]X-Yτ 2P~\>V?w_w1}ZcܳmNh^+ߛX^?vFjL۶ mW?s<Vetmn0EB +@ b4@Pq192$;3cBrUŗxvk5 ~ٱ~̫ +E"#( +!xžo*\vv V_#,X$N6%@ ߸gB% %IE? +Y`c;'efy% 9]D&eF5YG)|S{u^s/ᲟoD#y?W(i1!fbUrQ>Ʈ7_Oj擙eòqM3>5zIOb ^,˱niX@z6VH8l6\{W?mMo1+DP@iHHQAYblwfjSo;o)" Fjc~2V]&(mBb7ք=*ꉧ\ȍi! 9CLiƒP֨Y-ph!44Dt qr2 ukYBր6m.CRPH,; V>セ<=X?w5lt`%O` YW^s+*Wwְ_EU" +lXQ[As܇]a{$|WZj?ߊu 6mr]\k|}Z!@Wzz'U&As!dyt]v-YhOq\zAggY=PNAW!!؄EsDBZOafӻߣ1~ԣW5Vt +gTڮ]^QzI4T13=ԝ&famj Kc)V\f3~pbَ+KMC(Tփmߋ")!|O$ޤSD߈Q~>ZOR`a:i/v؊EAJw|g>Cydd≭Qfeꍛ::I@?0a'،ݤx SWQWERn0+"5]A(@AS(M˕Ϳ%yݓH汣O H[ŔGab %5&6`"bИ g><6\bGlI,WRQrq&U U+c/3!ԭFjk%ƻ3K*6w_o7 &lUDe^*l4>5Aj<ΜZS Jw{I1r:)#6NIUVZWqAZBUbYPvC $} eF1\k布6%GO2w}d䞞$oC,Qc8{Cת=j"9:Xv,(h0.˽sio:›'M!)=x!=޽GMӮ3G7GO}k+8/bp0۫>ӳWFGhRظǼ(DE&iW7 U>Q]O@|J#R(T!Jrħw:!Ȏ +{9wfǟˢDNT{]$'QrO 4snI aڔ?2u7Ga0Fاٮ0išsGM_ɍtb_'x m^h=&|SjL۲Qe Nn^*BgBw\[u[R#>*H3 q04bv#[Lb [fiy#_)x1=%ϭQo0)Uj[i]`MO&׹66wwGNҴ{?5ݹ'oB@maBF˽<l4 3c CP$gpS%PGHf^ +:3ror?DTVHpPb  + i%R"*kAoc5#7xwbe}ELUR ,CaFJh}EAbm9Oz@J#ܔ"$8r9WG]dvwQhg(Evtٔ[U! a;ȇC8](Kc_:Uq PЛuGIsZMw,TiIw$Zzzn5cԸJ;VG۟1-و- +3ET\6Ͷ"ܚ]W6WZnhoGkjN 7OQ!l9`20S!J2ʶ԰lD[wb1Խ;$eljUԵ@-a~ ^E. ٴ} |H\/^.}_]9'V'6 +ۓR8bϽIN#~R]+1#{NJkz~ϽtfYORKo1*尋B($JHQA"X*C}g{FI\g$W_Adr' +[Q/Cq em µ7 +{%f47C?몦X{J0A,;ax{`OY +BpaŨ'#u;wws0MP1bm-X;ɼĆ-roBRKx şm",wZSW$0;#Gk<(N#o@$Iވ>LAJLlNwCT&?;W-MBOŚ(wA}oY,`Stx~1rc5?9 ;t.R\ѾS1 ogvɵY1#[ t>UFTd:LpU<Ыήh]QT.b<莖Sj?\oܿ _vjo[VQo6~ׯ"qܾڱ,M A$ P`:[Di;]@J%h1`_,Q}苟\ GaꙔ:$ ByX(<8A vZyqmݖԲ`HeWd~|Eb ^ΛggsY bٸtC4 IK뀷 E5]J,,+kiapo>=WQa#"Z F8Iҵk*VH:nmG1Ѣ6v\+ P6xyxRrzK?3IpV5=ߎ+"iLpBl\'q +{j黮pV""LÝi _9kiŝ1uOwKVҬskbԧDnNnZ~NџI57e&yb NKߨ&I}:<, t(Χ%bOaoޚEs_[h8Ͳ^w1!-/BG6Ռװt*!8S~_oIQmAk[1s6&mJ0v"(Ϯ)EviݝF+: + MԄ}c%}{=]aϑ='+C鱎è/(-&a;ɄSd]yGﮰWcH VzYXDM;Mx7X=~,~H OzS'/KBM䕰g:OOۇYWt,p`V^ JwpٿV|db6>qCZҞB҆\?m`ec:og:Ct{ޜ6T9.iJL?l9EOo/-/TN@+搃Q(4@A*RYi7q+8"͒PK9j LZ.?edO/%( PXTh#LR ܰEb,.[ug9^3TRB׾栝Y%[{ %BQzӽfhB 9{ޠ +KUsn8|WK5T+ V$%H>5vyyXFx*?DI +& gJ5I>\/C JzEf4u(NAҒv +ݑEWo|[_s6׍!s30 dpT sHs([ ImqJzz5džzV44C鿳8{0cFL=!f}Vf{09q?C_ai ']۞Ɉ}%)μ"Q^rGx0*_XrG͇QUD-fj.ӬZ_.8& 5 R]o0}ϯ8 +| 1TUIqnkoh +Cd'c߇2 'mS7 +-e(NFٰ(%X < bÞS)>#| bGu{izU>SEql?BK rhͺ`3$GF +%ƻK*jCۆ~xX-TLJ%تĿ^ckW maԆbPX*ߚFFYKYQ%Eokqm4W~Z1\edM) Geo[uNQ@H,JBȃitWkk4T(T>zw2g&ɤ;uzQ%WM4 +ݢrPΖ,tS`6Y?譒h}baj|GMgK-#$ւ:ζ"2RdOms)ɻN:,&_JsٜS\!M*hO%ب?_E z-}a5ioբ7XmQ +01܅E mj +⃦t1J/eEʭb&)nag<R5]0wGVx4=&0 G$ tH e{I͙,pF<*K&$M#zK0.V~eVy)S<(*_5 Gb +SL)MU ›pRyضz#aP=e -C[XrjdَKSs+zNU_l;͘!C3Ú ?r {09z r/m]RKT.UP&nl7[arEZ=+!7)#܇W.vPf۲e- Li0zε"T@fqe !޳yGݹzʜCKpSh-mgLJaE=ԟy?FA[\3A1TߵV$`/q?&a@zsWh<-YXAM*Q5Qhc% mgJT{IvJLk@Y !R5e ʩqFԠ'@nVߺHfE${oӤky8Lkxoߵaߏ)犛pF<G~k 6'> s%[Xn.KW"nr 3oF&tz+;pw/ hG +sp8_i΅#Rq@+ [ /pTo0_q Z26DYnBe6Uƹ$Vm(';ᳯ>LCX1B:wY GOVUE.8$^#WҒ)8%C{DG:a:r];ؕ6#mĔu]Qf9DqsXUj;"S,Zbꀭ3m$ZFH"v2֟jʜZ5#¥vpeG"^"]|(v0 =Xje|gp)RKmNYpkǭ  2HE1R]^%[xa3^C-~&A ;ZRsƆBRT*N} {)ovtY?ΰa71NχykL?w:!aų={\ wT?-/=PjA |߯<$v4!RB]T(=ݭZH:S.>j4c .͕s#{ڄ+|lТ:jGL+|R!ܗ(nF/uG>s|[[Vh""JTŕWOUЏ w؅X\eV(tEM2NV;K=_'g+ܷң =p s+4Ad\sedUwN+cs4]nӚ. +FxB#p'ҶUFe4;8?&8pKhzP=gl%+Ҟ uc\'X]*$iu\]]P1nc+ &F(O,E2$cG;(+\ZD 1|V4FCgccőP(9cᜄɾDT]f_ >9Zket#]K?##pRS#z( +ݑdcr.>";tV6dmtP8AvI6/nBZQQ̚Dl!M23yZjbH/N2^d9NR|yߐ3',yLE9G>gQؑ&rl'Sbe&ض~^9B/.-M(ߊkqXHewyjdVnF}WL #V7R| +m,CvVP\x]^&QI +To9hwt!@0BXەF>17q hFT1cÁ q4u&p#|F.Iύ;><˙0AKStQi) !P$V)~P6F! XwOт( \I@fY?C7- 1`Bn^‚l*ި@# f}̨F#[[-z#Q"!D w\[5tL p0!&,x1=9g4Mf(mwNsZf *i~bubϴh|ut2<(~nqw0 g/׃ݟІzwzr2XjY*9s2ܮ EtqQ-[a+K{OjJh'2H,3Mlyw4=5w eRoՃ1ia)HewYN46+TL._SaO0_HR4iZ`mH*׹4NB$aB)w~;SHKf(f)'& 7H$*+S?B(ggqȌy::QAIYN:`*1/6nER=Z +NJY2'",a%.Χgg22P2DM/JP +yVNbK9ㄫs}WMn*$ +)c'$Klnr$dbՒn\ȋO:L\~9:9=='A: pbVF#f2F1nkxU=UbL]ahZvxwn$?u]FcH^{mN{P05CKvPgt 1!cB%ٝEEɵ,H\UoZVpCM*%;vhw^ +;q% +찷~m +}#~w5\aTSP(W!5~n +5ida~Fm\aIo&Ak1+ޡ8^cIk\jHZC] %13M(e7>⦄|tBgj[!y6+1H`Eqb 6tYat,synM{t 'nxSc:,(ܖP(H.9,zX# ,.Fi))Ses@䔰gGn\zX Qc1 ;Ejl6܉'\+_UHMpEUHwg3tM/FjSOFvO5:5y"'ONvd~%eiNf7[! /{'/uӔ;Ak1+ЃLB&165ԥ@jZxm)e7v 6b͌J(h''4R񬺺po!*ژQQ6'08\0cH&&b$u}֗ }`GS.3u$⺄B\Fs6ug,z +\RM)zJeyt9OP9%Vo{-P*OM_N1)F(́˒@QH Td-5e +^9@Tߍ矙-AR 3)c;n=ڍp+S'N:W؊`Z7'5bYOd>kaé upЄJ%ƹZ1A 5LjR%p0-ZI4AB N +EݷjasP YA8-I"H-## +NH\Х-Nd4*6#tp1BX +'BާHQ$V[lhJ|d4YH\9JnV/,-Ctދ{`U*k/ 5Q\u䮓&>xXFE_i^o9z lC^-i 5a{g2j5~ 3`Zjkl](>nǣƨ4V>궏Y.Q,ߟU[9 +{m@ Pf਺ L[d²@/y?6&˻"H97(cy\9HïI=`~%@6Iȯ|bY% e9*>`uQMk[1_1lcbz֩IheZ"KjP{ĥ'jgv43jvT}ᬚ*KA#!ԐyeLYe 7ĕFƇ@ +椑cY1yEń">rˊy9^,Ϝr5\j-i9$yFӆwRA$ݐIcT[]t|ݭꍙ'Î +j){-|I:K򤊴1"lhaoI& x,9XmQk42Eʟw^s}ܖH 6SQ̖HUt=ĆVm5T00>SʁZ6Vp)jmȟ}*PO-Q:UNq +=˧Ͳ]So+X~C.Uc$[GX{[V"ԘFˣ25+EJG=7@GX .,x)"~6k#;{ &Sm~mN1~9B OZhDE r خwU}~;w5TxdD[t]*ezq<| Qjpg{bVSAqq2>M2)fܲa"U & lp%kˮɋMi@zJp#c"UikSD+&r?G}0 Vًۨ:қ#\&5ZGaY1]α1.Y|NSf/Q2P2E7]S̊)./,5śAl/S\]MKC1EwiKiqݪR R˂y`^2Uۂ̝]` ]vLrsQߠ7ςEј|(W O**i9eD-LgJ9P' +.E-i5 |L u9;BXXȁLb6YͻUbbgoUرzhW^R[TydmHueYi1.X1R9s^)V +nr +.|]N@ 2ORc%DQőNȩ54#lW*$\cBJMaXs\I夯,C; ld/JP:שL'g-Jp ~4m#PKNu=O7]AKA +CHW[Z*D +(t6 NgLZ.m=x +I͛LϨ+4(*]pbƕAW d+`F=1O ^"!AS+fs>kآl#% 3ac +oZMRNIд!wt`G86IvV9K@>rxY/U1V5سzhgVlylQV2?6<14]sv;&qUf%?n(wO?a>vءizؒ-.1^N̏Vo6~_q-J2gɌd+Vd:% :YDhR;Rv5@eYvzɻ>~ c͸o #uδI(vg-IT͍%-pɌ_Q!19%.QYԿW3nn>'bDA9]̥[ٌke,܆Q}'Nӫ&p]C۽dIrC‘wS-"J U{mFz(qǨ{i%T ɊCe +{mZRp+"+}uLCݒ"ш)'Ps~bd$iC:vZ^w}'y +Hr0F1jsz5qc1 )LCA]*<E,e.3@Ήh%pm=n6%*fcQdʂ "RŶJ{RҒs-挢LR0)&Pp$bwyVk[5f2b`O4$LX m^RQ*@uA(]FJ>8EAPjMPD)a|Rޞ.&r5v[bMD8]L^R9Eχ vR@)GC~d_úqv9 pgxn^xv#֢>>|gh2<1favLy;I+oVGZ}<ڡi0Cgm >ESZ; } wC ;DE8kGjU@w\wO[_Kz/~nx!CMZ|z礥nEpuѦWn8}W.,'ve)6@6)/mX""*I 6/uˢ~93C, 0%} ՃzP'w||NM|#LϞ~8w!O {D$|ZEx6;(a'(TY!Rԁ@X!gJin\JfyBXS"͸E9B" K3RCa*! +DBD] #XR%E|12GcBn~҉Rsc^B35LPfQ%Rd|*9Sx^)Q*8>8@ Ϛ]#"^Lz$eq ' iJõ懇3D*7NΌ~ +In|3V0\[u͈_pIi `\^^&:|XB >)Pdk56,FeÜQŏH l xYlKD7I gJܴ@ mCg p2MZ:.TKqvBw 22k#Eטq>G55W]0: әFYѿ@Dn%zU9jF +T`ݯx]S-;Fѵ*N4 +usGDmps +ٯiqFt/߮<)Ft>]oaڃ1,8Z֖IRel6ڝ[k[yNKihs odX5+ ),Z|mkֺȩ@9nTW5,}x#`qtc}QkN[Uz~Gqx<"Jȱ3;0K|PTx0\aB6DWH Yt.}"M^IX~5_ aV?l4{ &*nn&{P% 4N{__>e?AB }:z~hE~*9pn.ΨLPE}?ޠV˕9&%yTʬҖT +((ԡf{hZ_(%c`孼O8O\Q I Sf:P +mIP.ʎ:NRvc$4{y +1ı/$' SE7lu` D$q l +su@c\0K}.=HH( s O<,MBDcgqa.F8]WEX;HfaɌoFLj$8^N>T{lRJ(-};(AB$sf LOË/Ї ف.O/pr58>6sGWW? /x<8?>؂9L.&ЇZ`\bk<: G`t6TUōjײq89Ї6-8EH2I~P]d2d clxJ J6fXIx~^[R6~aHA8-L+V{Zm([ 5ujp KрggH O + +7W*EH8F +j +ӇѤ³tF֝]3{@l&UQAdI~߄#IF%YLjẖ̸zޞ.+gųAbΖ%-ʠfX"syizN kh fz`NIbjK&*eto6 ]yeM^YWVV^;wwr|J~ո4НL׽i꩘j{#tE@5=] 8؃{L(#erVkKRѸ{Hlt ROƛ7<%wA NْznqRDs[YB};f "E-@O=ܙ=XG@WQ %XUĄ5DzQjn Ga +}3Ofh4 ;~]˫EdW[.~ y-3g{xߌWÍ_į="i7Q6e#Uݐn+$N]|}eys*JyqXe\mY׈2h7cc=:L@A*yǏ~-rDMSy^XwWr Z5f!~[ B^(L'XSnv`ɉ:իniUmQdn߸^[Iqϩi.tL#iNYZI4mf}{1izԳ0k+Vz{wMgE켨%}s<(n~|B?kz)xt3&v+;K +@H[Z rfz-e{,%(1@25Bx3% [c$EwKo6l[\Yo:\[?jT yCVkj>7صuioބ4ѬXPQaڒWS6[[Z]6S6\L.Ԗ+>|+{?[X`l̑YbmE2"qCW[\(kstrySQ*\c~z^YZyFLjjBd6}fǣ i>E=jdu?ΡHj-BgRo +7DXEjp_ަ6z§BΟ^neºX_u ˹vR""@&*˶}nhZNrtn'p9^ꦪWS ^4'qxa}mRͲsQJCA WC[Vm- "z$o_^7]vZVIf236̃Y1qNxd0³F1VMo8WL4 '^$btCm"JZrd/[Rd &o{3, B.hμȃ<&B,$1CcxH/=("nu3bBڤVc$2-νA(a[f!̅l%@Nչ\G䉧X6c!a^.y^n/]fYgLmILQduNɧTj54+ˍj?`2r |J;'d4)3#6ξ3/~adrN\VP?=C:g$Hvv``⶟^Y  ^ThIHLp ++e3Ͽ9v:'UbZghKo/x4; $GbQ@er=)7>x:W-u - xcĝ4z4dE{a!xUyii ,{ +pfSFaK+KgNCz[,iSJCfV^X6LhRqeɐz&zA#0bVpT$E7ָO`~߿Ŵb&6[AVS̑9ctk*G'Q :??$j^9K}NOgSn0+KN$nZ)$\1{AYɩ4&IokngLDj=\,ܚRUqJ% p[@TeGj%'49_"khw{ǴVerKp!JBsGig +}}a fkR2Uڿ\øtC>tAϐ#X*ۗK$^[H;SwCԊt5RMO1Ẃ$C6@C#"U nm&ت׶ff^y @Uem7޼2Td2D[}Lr~<5w^ ddTí+:11)?_:%GOOb8C4[瓊.(1jLYvRT_X: N:qS<`i}_]V`PaU@Z6Uȧ&bC:KdjL+WE$ZOE`+])~ Ɂ*0mN'ȷE +wLM_}a~aŖm.lZ!3>.:(I_ P8О̛:Kbz1%TA`(׉ P~5CyaYZaAӽ'mqZCu7/Kyo~ *5S +{R"Vn۲k B}O]Pj#1+Oȃ\^a!sBX"$=q̒_4CN]=.cDxO,Λ9;28£d*c E7N1 Jd\\T0=̧ϏD=E fr.˜rJ6E-4Mgzeo9 +>-O90 n/7j5SG :/YaAkyIC C3i˒2V2/25fO^Ey;~dj $Z +6,R;7\C6Ɏ%'(J!pW8,aV_aJF=5mN W0(HݤouQMo@ẂHP@hZJTB%JsZcSTWuhsx;yǪ 'cQ( *lt͇l [P(%7t@ -R'q-g 0Aqt99M etjS*+t9TxSp$Z`mِ  +/;Tn%  ӾwͿqTkLKTc+g-Ak1'B`Y,v#$ùtǖVke?μ *N(d$bp vμ+x K.ʿa +]ݎFѱW 7='zc@Q;i%FӧgZkU2PhԥCl|ыvrW2g[Ҹ < zMn۱ մ;4kdQ%je^mQ/4N_?SMo0 Wp@A>f]+Pl:;(ʒ@ɂ}'ntG>'/=䨍bL0iyp1$~}YR \V8JA̜0-KTg-µQY\(xhϣx +B7"+_zt`9hgiQ#u R"1mH d Ǖrvޠ ++uw7pK5¤Tk $%H\A9d1%͸P#o'3P˓ҽQo~oR= ߥhA;w&TQOA~_1Mh8J*jBѢ>XC9n㲻ٝIo88I>@f,$$:=;)x+KKuaIT҃E`R䂎#(cWN2XiBB=t1>N4E5\QNNfU:a4;98_6fpFJvh%iO ujY=BҲȻ GP2,C"%Nl$w#s}.&ҋ^Z΀jB JϾI$Tj*L/Ba='͗9gS99:@gMw7SV$gXyΡz|؟\>).a&Ya L<܋n  tUaU 4OM& R~8y% +g>v[> 4KQlRD ԭ*d +q 'ThUɆs!E婷*Ž3?I*uz*I<$wu6\prG/iS܏Yj}Ʀ@}Wд-n ~!5>fgˬuЂa0# r~)YdUS#?WA*{D +նڕϺ}BGCJ1WΚÛ76K]3g- Y[o6~8Z +e{7]a.h -hQ#)>("e%E~~ѫ6FFR :^LN'p +[*aMvK̎+? *ވ F?P( rU?rw"RQXW^̷9$K!tU(.dE;.@mcXz7 ffk.vDQ gH$ž9ۻk eS[HH,ia +TmAi%/Dlʜ$["nF#ɤ,єʇEwqzJ(#d.h?xpoys}|2{ +wK&;gR B3uKԢLJ$ +F,ٚn )\vLsAD!$f~tOAͶbh!1DE5O-&fA!:q%h/8.:TPD}D=i[4r-aW?RHO}d v p^ *P !@M a9l@UlYOπLa^ϗ@h7Oaި*]ES-t3}J$Mk2uٱEǶ(? M2Wys`6 |I4D pS`ARA| `" 4!xbcj1rX)t@Q aDS80vxGؖR*M✵!VK@"/_s30wPSdl 4`e{[(l=8_r{7+Ym6~->Pߣ2btmqSs?ֆ'4s"0S%W[Roz:_v\}wœռz+I&MFro a?hzw+X"Kf/ 97f2|[e ֗ $M6[M1D}lI 8U? 6w`jѐft@KHq齥`UKTcLF9!S[Z{.콧"k.$[Ӷşhzvf)0x+;Tv +/{9T[&] ]Ӥ׫U^\\@V0vmwk`F:ydk!u?ؠjZ.10O,][{tmY8T>^W, a1Euz93QZ*^еRj] , +lwb7sHV|W lGvdΩi<_R+C!1 9EwxУf_KuћhΗ$1K8s3=5en4L㬷sErϣ_g.DI¶'CcͰWVSMk+o C%a9d\\o!+ +Й-׭a0({]N4>lw;i3bsłոa? ~Roc%d-?i{B0toHz۟jt/%o̭-4M{}G wH*0ɽPHBd1|Z[}+~\=x-.>mHV~j]eK1mAKd!O8Z*hACkuue3L/ o̺˃_6tTbԏx_}^zrVJMٵ`+iJa{֜~:!&3}`zOoZy88!!Rˍ.QFSAq_JxXmo8ί*PrvMmK8Cc[N';BRO?3h&5#b +N0 [n p +S!,8oAi"I "ށ/V!|L=%G*<,c<90G)-©I j#S1pȊINں\[aK |PSm3FBC0Cx8 箯Fgo2 gQX2Tp7lm+Gkލ gL>yϵrdsNQ0OUyUb)j)+cPS?>z'8Ef&oڈRRΚ:kΧ:{qialdK*ע'Xh B~NR ۝Xإ3@ R hwųp&)*XV k >ofEڗ8 =H]jBQovE) &Z֦JH4m ӟgr)t;&䔾GYTh@fPI֜wT =޻b7qqLLȌOwƽyS"s7-fF[fh T~*)33u1)CO4K~y:u39j5?RK* :^/~n(HCFq<]FTIf=(?KVr)(=띸|-gQ{ǷzjUo:~_q*Q ޫ'hvjto*5c[ mծnoޙ@\2#+8֠KN ݮ +9_1ǧZʣliܝ~YIuϽǃFگA#ϺQl.;EQMk1WC!$-y6mp[CR(ZWT̬]SnԹI>e4e*D;L2WV;/h} xAH-:+qS'*YtSW }`E6Ę&wR4ac{M,φ_C;Bۇ>wcxcէ8Adywj1HŴ4^kK\jMlᡳL?e QL\-Z+c\"Xĭ7.9@+oMz,&eb>W{QڔSga|5S8Ove|Ey1 cvr56> Az9SR8T]O@|JM׆RH0MAPPt9:*ʎ H?] Fa3)^9?ݠ +(<8A 6Tͱ@["5K232=-{8dtynxrc +J`~S8uC4@&i lWS$V@k%xeK`e8#.~hT1NBx_ka )paۜ$r'0"CDN CH7r_ed H&ORyݷXQD|ڸ/]Y* qù"li DAj=+ۅ\5\2C7ZZpJ9,Xɪ).j% l;_N;xI]\EiL]tvKKPl[ZK>fcpu zH!~>LXωS 5C}S 4Tf֐USFfcd!Ƞ hTk-u7AEm,}|>wՖ0??ڟDY^p}{ʜ+h{c>M|32V27T׾OL6˄ύIٮpc"aذVn6}W$NSGi }M,biR|A/-N]>Y˙9sH, FÐ)SdHOagpҁxLA4"Ȅc ܧjg(Ua%bE%ưRN"H-:F,2!SnׂaH 9~׊m3Q["CNm<Z3P*JdCCm밗1uv%f(#4H-VcTΚ\3w`prWK`F̩`.Va`r A*U2BV+c-qv13tk;*rȹ3`p: Cت{a҂MbKB-w l[53y3AZ!;@RA7vQ{fU7_AnYث^ס*.]l?wU7Y೅.-!2;%)UjWBʧRڐ,idu9t)5NHVK(ߔ~rҞ&VlZOhkX^Wd@{tKeb  \5O ѽEKL9&9?V{b돩[ޫ~Q9Zfo,s4z‹Yܐj<-l- e^DasVlQ1r"ٮk8| &H\2utro_3dkr݃8I4˜)4T6kHn)7 4c\uuAOmO>KʛX6sjSrUgc* *yCmMN*B͈IvBnۃ.6'h7myi|nXc=l{zn.$}iLIwr4Jv177Xj1\onXV:<7QyތƵLT\'.EƝ\u遣Z'Xyg(o|IɄja^</,*}0\?O(S5>ߓm!!?DJ' PX]aU?VFO&-=4:v&S|=q^lCn>p*tR~|ӏu‡(d|EL@ $jdENWi~J#d*2;lGh]2ť!&}6$;{jS+lD^KlгSʁ:#lRVS§TP!w8֩4V91r +ӱϽ[ՃT,]T8KybmHuevGƸ`EhկNrw+*q5Up̷EJAuLB0xNh c@&ٙa7?.s좪=dQ @=gLF#{,ȶ(RE{!K$<?`fK2Tt?ƆvVm T00>Sʁ:#lR»VSTP!w8֩4V91r +ӱϽ˷Ͳ[ՃT,]T8KybmHueGƸ`EH(w'NJl79PCQW|EJAuLB0xN1 ǀLf{33to~]6s좪=dQ @=eLF#{4,ȶ(R=Uʧ;!K$<?`fK2t?ƚVm uT00>Sʁz#lRNSTt!86V91r +={]V_uSo+Y~ƁC{xI]q Dےd+o2mAՕPnqt3@GX 6WeEJAuLBH1 ǀLf{ٙa7?.s좪gQ @w=gۛLG#y,ȶ(RM z2s!J$<?`nK2O*cK{+6Z*EXd)@6Vp)j}_S*PO;w`GQT9^vխ[ +*.pd^R[<56$:۲N#3c\"X%Nrw+*q5Up̷EAO1+xE$Ɛ m,mәe!nE޼fz]epOxz,7I5TyA 2Ev +gdzB޻cpRrDB:sOeUg0Vq:w8^rǎr}Ad[ba-1.ZT̙_:*ZDj)O0 EAK1ؖUbAfgM0Ml"w ߛ7oerpj0M (· b718M*XZN.zV{h c)BDATV3oCf'4mp=$G!)J$+Sn=mכ_5SoA~FC[vKIHR#m~>0sl{7=9;*ARç2mOk1"T("أP&4̬(eo͛C 3X3&D< 5( jvIgA@# ݻL919мգQYx2|&SB +&ɮl%f1\ ׽3Bs0Dppt̽n٭Gͨ_щt9Ln*8iCZqHPBII +7S@jKL(؆ +3L +oKOP_uAk1mR^m'uhK =X;]Kbf֎)e׻N(.B3O7O($8&$ :%W n7OQMoIlu(;fs6GkU}7&$ 'z=oHak$E^EKkA+"JΚ(rlo0냐v㱋z~\BE֛LVHnob:*0«cA͞d":>Ф@Xt;c'o'eMP^IEwc4l\3„ +6\b:Bz޹=[ +BPHL>^,7eSgG#X~‘A;xm !IuF+YMiy[@'P W䩡U|?EAO1@<TcHHbJwNMg0nv9˛6\g&td0«gÁl"x>Ġo,S,7]`m؟IE7clioEF ޵;BXXȁL~y\lWݩ^LUbeQ#v.Ug婉!6ޖu#(w|I)V%&j(~oEOO1@<"*J$?cK<, f:*Ye)@:Zm#_c*G`[w"|Rȁ4]u9_L4z"i95Lg䱊&VNet(efSlNCҖh(瀎B269PMQK}EAO1@<$FIHbJwNMg0nv9˛6\g&td0«gÁl"x>Ġo,S,7]`m؟IE7clioEF ޵;BXXȁL~i\=oWݩ^LUbeQ#v.Ug婉!6ޖueur;<+q5Up ̷EJAuLB0xNhH0 ǀtfzٙa7?.s?feX6 +D7rw;VQޜ>0 SQ|SO,R>3c@ +fT"}c~-IS3\01.sʁ (Z}p +16zpu* Oqq|^֋vٽꋩ#ő/-^+/-aTTLqTi/2*H(J!vy2; >)G+]- G\ͯEJAuLB0xNh$ ǀtfzٙa7?.s좪=da*<-軞3pZMFFxs^PLEjl?MޱH\S %2O.*Q<_clyO"VrL.<)haR"ϩ@nCoN!)0}eXnݪL)$^~A;xIm1 yREjX2QY]iU@"XoW$ny2;'hᨂU}W?EAO1+x%I!36YU8/͛oeX6 +D7j8 +#8/}`xAH56xRw,S,)f̩D^[ޓGn`.ga]wFP0)jVS‡TQ!w GaXҐȁI>^nUGX/]lqI<"5, c㨬ӮȬL あMX I9Z"&n8"~UEjAuTHΚDs8ۛi?B=&cQ5y>"lhaz$ x,9XmQkG3/?x.lSQĖHUU8ĆvVm+`"Wc}AXwMETPB8֩4V9!r +#ө[-gͼ}So'+X~YTmyI.U#mC#-˴~V/,gGRۛMPT2E_KBASGQz#BG!ֽsݡǽȅsgT--Cϙ?15 -Tc@#1O\x=C`'Lm4^QEwCikEF<Ӟ +rfgJ9Pk\ZxT +}pQ:*8Ddp`:vw/mh_u[ +*_U8zh^Ҿ8K%Dېd+o2mF&Ƹ`Eʻ'G@'X 6WemAkA +!ۤ1N$84Ж@z yV:$Sq HOKi +*GjPX?n:qgu($\ ;r$lȏq+q)b1ł$rs_U|sgސZܲ`²4s A$lZˢGCXè?:I!Y^BN(I~YC=)+5.V<y򌧆1;*cts>*Շf*=d$BUmn<6a9Im4y.lnϖ'A!dnm7ώ{yJ{r{wEKkB1g"JڗV("إPb2 MBfJ{.pΜf]%tKF咉5( <YAuH#1ORL%fAO*˜pGAxB L*VZ|C@ GO./6E'ͰAZxNM1y6eG&JIj.ej] Y(Z&:) +&~oEOk1"JV +E{JLfl2vq͛˰d.4`)Ȼ\3p#ޜgT><#"HvΟh9)_pDs0DZyba=5/P{a]FhaR⏍T P5! E&XRk)i&;c@ +fT"w16#QO/rL.`]wBP`Sw"ϩ@nCO[N!)0uXmݪ:RHPy9gqWKjeT]*OL%eUca,sw +|7UpENAuB AE Fcx$1l/3qvf2fWc}v@{[sfOx`0d*TcGbSl%2O.*R<^QwCyK"ySYvS +AMQ5 |J>V( TR90 ,ꊩ#ő,pmyIb6Ucad+Ge6G&@"|]kw^,I9V&n8 +̷EKkA+"JΚ" A0G!f3tB{M.m5` D ;}S&td0›gAÁl"5X{Ġw,R>DC +D󤦛16TYQOR\]Rac ZME +SzBӆ޹;Bؤ)Y!}eXnݪLUfV%\S$[GX{[Vidf V +<:RۋĻhGQ|EAO1@<$HbJwNMg0nv9{͛m` D ;}Lr}5`WςE|AXY+n **i?ϓnފx +rgJ9Pg\Zxj*|H u?svN)Y!}i\=oWݪz8ZAˢ +GVKj#T+OM IӮ^GNrw+"q5Up̷EAO1@<$HbmMg0nv9{mv@[}r}5`W /TO z2ߝb`+q(~pQJ֩4>1r`=[Ճ#ő.pXMyj"5,,c㨬ӮH/,)xu'X v79pQ|EJAuI4"Bc@fg{ٙa7?.=vQ_OϨ[h$Z雞3jbn Yp ۢH tc08) {]DS +DW1vTYQ/QT.vj!X¤BDjpɄ׍eѿ‡,@41Ȟ8mlqN}HN ;)4Ξv ̂3읢f{k57 >gaܖ8OX':}dTU>:U&mJbT(4IM'<0(ՊEmdU}W?EjAuT%gMb"JCNBg{3MfgwXEuTMϨ['Z點3Mb^= jd[F]bW\%TT0%lwѣΊx=Lb̲ϔr6+np +}p6Q:*8Ddp`:vwϫeh_u[ +*_U8zh[^Ҿ8KեDېdko*mE&Ƹ`EFkvi9P89ʭb%^-nr +6?S]k@|ׯHƎ髝iBu |Z[Gwv($KdCܓO.scL_n>&h<`?2ERA8 +3ū;^Zg L7_03ogCXR+aᚪZ10)Hkثea*d==Tk%2+75Cp!lLJb8 ;A**ga +;pXl%i82bDx΄? EQSed HR8 ~=$(;!r^^Ҹx!MOHjA= \J'q=i7a*0YCO2LjHrx hraWwAX,5k9`]7"qOЧa+dh&hQ,'IZ66d!l.!lȹ7`pבͽNpA/^ˑ 3owG7]0`\ë<1֪aCb4ò=IUZښw-V~.qѧ H-P0M&'''67iq?_oR՝{.Q1W} .*&F7R:M{ K t+:os{qCER O^I^QꏊaYZ&'<îPhu65'x 'y oho+`A+X cՎӔ@SAZ^"[{5a|rOVL (~+XY3$ah߼x?Z̓Tk؋J֋*>%Aڪ<,RpBjQQH-Bd  +%;L&ɤj'p#v+L5*i85cm[k1WB&kKͅJ 0FIhf^$RfӏF4l:J\&gN"pZGFju "%Ehqk݂+ q<# +N)y>o 8Č${N8m,!vA ^l +~ + jmu09s0oCz u"vLX8^˫/lUSK% 'Z`Bsd&4ۖǕHqk)݄e"Ӫ%sy+Òߡ{!19Z穃H3Kk ~TK;g Jެ 2xeY=<< y^O:C4eiG~3),7~Akգ9695j +&v^߶kF`\Ե_Wƿ9zGGoVcvBWa,-̾}PJfG{{ʪm[k1WB&m@)dl$HB3k'"Eh47#}&!T%Y-w?폪_e,#$-na1){#u.) L%OLXqM%&pMD(@/: BB9`}҃>#ńE}OL'٪4&F X^բ +%MСٴJfÓq~s_m沪}Ao0 <`i]4Ztha(a-30EH:A1r&dQ#=]l@JՕ>guc&##/ҕAq3'P>q$  9Ң=:aI-zpO=1XXd)@E;)*r~N }`_E(>oP}cȁPvCߗۻ˻b5,v(yٳWZԳ%;<17$-C~H?Dcz)sE[P܊~1[QF` +ݣ8hCQo0`E2R|*R취Ż1ZoOʦ(ʽ=uy.js3}®潬T-/F,`UOLs<>j"#.ڔ߶gaWgY4_m2'%[~2zwy1uAk1 n fm%%RchK-gh‡EnwuG-<@QՋG:kd:#_BOl">E6!Hq֩7n~h%œ\"ľZ//8yڄo::ϙ(TO}%Z&]Ok1:B!4Hi Cr,v+"KB3ׄ|Mی~hq̕hvVuX.Lksvjp ZN(+be^yn0(nce:ƍY ʁVc=o "QG?猅%sBf5f91{أ{Q{g9Å6 a䙄x;{xWP`ڑbKދlv2>[8 aIdˎ}=l6+ ̡i] I7$^qPg?ʡ|jH; QRgy9nڅ%lcn kҝa,~zaM#/?u`փ}N9*۲k֧]G} ˚w]OKA )VֿE "GAlgplkj=[2aHx&۫+x&XhcbDE%1!naPKKȏq#q(bFz}O<פ);Pr{!(7%ugEx_mW9+#;Y,51)cy7=.wO˻~`vh.Zt4靵g<Ey>2u|"U2 MgE?\0΍_@b{|e?46l}qy+C:8MƗr]OK1f+Rj*D +(HmqffEeWe~evUcEC!{`g;=v8*&E2!)Ci&ԝM47„3/Ly iՒgG7~97uAk1LBۦ1(%V(ٕ2YSnBs{ޛ +:4zij.bE#VRCqfbQ KgM"yb-u{zwqnZ$Yu sI< ೘v6+,01%g(}ge*c0oʹj.f b}h6yT;UpB\ y} ?lb#K_ئяdT1=.kbԴBsڸ.[Y%o=ZV7z`aꅅ>n~?VQo8~ϯxKI^3k];'j~I +'1W_$dCKFpZKg4` +!I"6)tb:!ynBL}GCQ]O1^7aKfI0-iN%: 0׊Xf %LJo-GeZiHhՄT"[~FOFebaw\VP䒷:3H(6ea03 ,Auz?Nj|>YD^Oa"]o:1Ovh|;VLO{YUcfV3#h <#Z_#c + %j"V.4lõ!rɬ(3~Ff@-Fl!2#1S] EMx_lҩgB >!J##tsO|K?5:Q`EgF9k\|)^hŕJ\ZEron>w%_=:=ǟx<+(  +Ԓb}rgcaRAkAB^LLH)3V/?ZENLJ +4__ʴpIYv䏦#wFhHg}!.uߵصkΔ҃b&G]z.8bSq&9GQRw]7Х9ztlU3v~Bbx} ['wApf)kAbZ{5uP(c;ɻ!V)(5ܿ^\= r_ȯD=g?5 O{~W*HE]K1E+c[JϭZ-D +Qiv$dfeW>˨*<-^2pf#^>0 SQ[<1eʗ?b`x((~rQJž'ߍ=zXssEvS܂XMEO@nB[:xQ>֩H}c$SY^vTWL)N$ɫ%52lS2&ucu[Hk|MgX?/m~mQn1WC(jĵ PR PD ڳc[q +ԓ3o{}]eH/*w}\^Zl_/}`LH#ΟÄKY)z;-ؒD7+<@E=E츲`[mvS܀haRTC$I1`No qLr$)Sa<w$L)T`}bWmKb&Yt0!}t2N5%KQ?;u`ѴQD' 78%o7݋G_z^,%ۮe(YI͖ FIGP,#KA_sHd6c 3*#ЄdS0sV!Nv:uXoEMnriڰ׶K +W7R +WENAuB AE cx$1lqvf2fWcW}` D ;}s&Ox`0gÁl"X{>ȠsS>Dc +D.󨢻!6l#\YRac5+|J >Q:*8Ddp`:v|Y:0Vq巋*Y=/RuAhlamYm1.X,U+INQhqrC@'X Wemj1z];Ӑ@)X0vU$13k㖼{%{43ξ6!,S%Js=5Ol| xAH<~G}]VQ _U0iΗ ݌6bA1f2\s)@66p)*u6]p'zQV}c@V;O}*VxyeQZK\j3IZ~H?LxEWȐ>fj]u +n;m)w!F,Hm~gp昚dLS޽V+(wN]TͻW\(e/,TlCAU}5xA;ݧ¹\.ƃ˖鐩֙9)Nn1ԼVmoF_1'%gS8KHc1Nidm`1X؉j1|R>+3ska7D k\0⊅x1~ Zn OB C>iAi2o!b&8#=GA38eĘ!@@.#g +)`0w]Cc O7$zq +שdә!RD.tӄ\.[y\ VؘFdj%\"#.@r9o s xbN 6m9wS9csBL̻2d|;V( v6Lui٦cڋ̰+սm8s{أP>Mˍ9S:V ,Cz4sLPFO8ݘѧN/nȕ%1p+I)?hqzBF>Y-~ + JYh,Q1O0'$C+T  v0"q!%^YjV;\1)KWKek $';#LKf[pNovz&tHlmAk1+!ۤ1&N6$PJ9X;k*]J{ƛnzz{3Z_в$P'kTQk@~ϯDOz,- f4K0;ѓ1ګpy||\|˳RRb/LdT0c!Y_0i/>>zz vUAް~H9"C? +69L3 x=~0C?ʴIp7:/b$ilaӎpŨQD} TuF +=yrL9۶6(ssMY1>9Imn@{]ٸLIyXpޤ3~d,AY>HǴok+t2*9sX:6^;#[q;?qۧfI6Co6V<`.'.lJ' @׆r/VhbTn07Wỗ~Vn6}W̃ Igl[đM6mA}[QD&!4{~S..v>U6Ds¡Nҹc'vn4U8&{} d\ pMWxAPCn!HBxC\05pH٢y?Hq.I NMpsJ t@`2DIK|QXE&Y!+dЂ'( %\@fVK_/.tl,<2)7moTA B&'f MZ},3284o7ooY!' 2d\ +ݽ=KW-5Kf \YoH=s2EB(mwT g`g+Fp! t[Y*^ ч-2NfU@> ph|Bd> Z:^\qgě{R%@ +vn6 \F\ +X) UyKnrL7/NŠ1?/c&gߥ1x:t;і9'?iW ֲ ;tݷ;hn1Ar'sm{z޼{|Рfm֒#(_ Ok7~8bxux3L&|ۉܹA&qG!݃zt\SGkw;j'k3mr;0P(q[JG FW~}3t6Xge3'YLJG]T[o0~ϯ8} :cZih&!km>9$%H/'|r-Ϥ$x~mGІ\yȔFP $WKDI[ixQ[-9HmŊ@٬< *'2.s}&Ydď{߷z^J\d(ؓHBqgbF,z +`0 ڬz8v7A4ȭeʙYFy=4,ZZH!-sA &i3tar)&z;4oy$Gt&&>jGsۏ[KO~\U~`Ҵ!cyfڭt{ l1L8NlGi ~o_Oo1)!@4B(mHQ%rf`GږgU?IGOa1b$EX]h!>'^Vko?T02Ӥ/4J#'50`H!:B? ^$[!A+¢qvՎ-y!_TVLMFŴ2Y6^4ǚd 6w75I4pW4 ۍ w5MQS<Έ(r͊ z=LV&fl+ 0Qmس_x*Mbx/l o#zOlm|.&?[B;;YّLhOڶȷ];mdIHTh!z>_Xmo8_1BR鶟:I7"^$i`E"u$HzlSB3#̒ b( Qf'o'0 K(r w [NZf[VFA ?8_Q xhÈQ{[ʒ e +1P)b/J?J&AX[i( +R&2D#:Ԛrx%b _+3 TƻG=ARw Q3!r% D)]0aPi|Wjo\¸ɸ[ebxBQx"%&ކfe$ X-tɔ.|`)47[`('Zu15g( 4㘢0F''p&\Kd+[ЧnV\)hf}ģ\q_L>ndhH'.W"oY  <=nuDY˱XP)Q95N{PjвVXzn/*g}{N/2A4ҞG+ݩU` +BLk| J&Hvm\jĵC4v3i'Q l'oƮFgІaӋX~f&Wo1mР•r66Hٙ BLh-u1;Bsc]%L'}(x;R} }dH WQ.){YQhn&]X#R +Mʕ-!.&9Kxp~?ԫ [Vf_)S7I xpcxs+W\3(e-b}v aIUJ +`P MX=yTbEy'.ׄƹ8Hd'šX,ބ! Q嶙+J@?0p)U#(R,!Ȇ5Lf38%#JpcfG.0SdEit([7#!~~oߪ׭zsO ٻga,|h؝1WkOFƹ7A?Q]'0x/K:%M;X\V"ljށlPS|1 1%L0*h +R4>VS*Am>==9-aq|>@K?R :ap89nſKA WWRAq$vә!m).ZO^<&yL/ϨRSqfdxV*쥠LjH ^<+f)U0pC\kdV0%|:ԓ/xXpˊi94ϜrN5\j-i1M + ׽Sq CbtC&)Salwl4P}0dQA-ۋk< _RR}|\EpKOz^UDcm,ςp4|TU kƸ?Gl9bgR0Ls4܇qMrWfix |oܭ/=?;G"rg<Κ|Eyr&ҙg(3Q.JU;z`4>IRRe9)۫:h +f[]2 q4쐽 鴕If/N%Xy1YAYxŘs]ۉPZŸm^U%͊Xl5Z΋Wo+T/*l qYU^d=oOx06AJTx_S,kCtD%اyӚWJʖȖ/OV|q?nz5_od +% 3w}2HL I}ҡQ럛$1QE ޓ7V[o*7~WL؍ Q_C$&RJ"Hڇ$B;Z5텃"{e*/ }3FE +hՌڅ)4_~1!̀"ڂ 9bhA1j*QFZ Ɖ5pME.qIeD=ƨ*R(GD@-c+.5!9GwڜQP5L.( lnLJd:8W 0 [b `&l:Fƚ"P _YQ"2/9.IuLWRrڸv ScQUH=+ +kp.VhW͡kޅ YA-qD̹kFI7j|5XCPoMBuVh;e2I'~E< b5H]htZH붆ByoB{<2}0NfLs' e̽1| LTKoh2R?u717Y Y|u^JR=3/r]%j-J~f{v|Lޘl̋ +!3R4`ԯiK=Pb]']R66hDkş0WrLH'CV~k\ +!%/h?ֹT, ఑[b3f  bM6T4Go.yasW,2u[nQEGf-|SML~?F#dTP2,Y}ayx7óK+ci!d"ó[aoog[q54uC^cx3k t%-Ɨ,;i5fU^@#4P~ܕ6z#,ոhҀoI9R0^p:plw>N|Ýn -vWSo$ + +/䯆xmAOA +P`4!Dٮ8L]UⱯ}:>&lhaz$Č Yp ۢH ֞w44߼bpS"6NES["ͫc=OTYQRT"L[lamyH#c\"X[[G;k$6RTI4*> ǴJ Rf/ =KKCA +β-EqkT,RNp:3$-Er]'92HM8؇*x.'EV^ :.Ţԓ.FaɄ')^2oECSlhgEBۥޫJ{㒧LaeXn̢7aGt4gK@XG/>|dg#i eIvmCnL8FkܷSO@ ~_IL$YiW +tUXWQ"t8i素}41[wrfs )BII~g{u$ \9TXA &qF}&5BA4B_HDi &8p-ALjV!0#dUQTBIAP)X[p :j&nsRV07LEAtrhQB"sAC3A}snҗ=r9Hƶq8u;Zh?/%uMrG|Jy׹?}Ao1+!@4)*RUEMnUw`ςP^y%ivo=ɇXEl 5]O}\xl:M$}e|]q,BeTSL+R)G4O0* +O{N "Yf#㢨S~79&^l.BiGq{n'#=,QJMEp$5`oYh0@ҁV)+Q!UXE+gK=*ޙpп!wp77^_񄵖N/pm$c_m-ϨT0+@tQҢ]iZqbؖgB{e֗${o潱2 +9J-<Cz1p#0K0zc +i @&i {)z +>[\ lKA6Z+Z(y/)HEc\ kaGp0OAڼ<Ɍ#9! +=&2Ͳgqu"}| izR-!BfԂXhy` `2 <f Ϋ`s ЪBU~MޥDp+:6.@tF+ @ZAUe7u rZsY|a݅+12C=oD밒:U[iu`bb +uyaʌ1P)bH "$9@EHä@Ƒh-Ý{|-l+'̤h>1IXZ"PG R7$r/3O/}%4|\ +maLr5̥H:W}mC-S{9SlK ClTmO9翊>A51:O\R7œUVR(W_Ilv +`8:95]p8WCCǎҧ:UwbJSd5[".v]ڌo65-@,DDeZ[fQuWi3@[ . s <؟͂lI8ڨJT<*fm>>Ly`Y*lF1 + +7@5^5jv92 pƧq N8Bc8+GA ֨̐%CrrڒbNMM+y1=|6 +dpÌźG8̒` +bnlyc%LSpȈuNآ6@ BK RpTAXjDfw~ޕ(a;f! # JxsJɓ@m8ḲAn/]g_}’ $.0oL-U'>is1shЪZ `-3Q'Ό2Br^L&(wVmp 64*O+6QnA E,_KYVL),yÊxtx 3O_+ܕ1+k}$πvps ;gbؿ.cqTmrUpU+U+Uh[-ge21kH#29"߳cL ܑ[k-;ɝavY;<_,Aw2XC)wxFp$5T!H1z7 ;K;le 3דIƍ0E6Fsfİԛk*{ZN<)&o 帾N]2g;>Fpmu, Ѹ7CW[UO0~_qh+JוBTM{\B,\;9Wƹ|=9KB5{ M>hǂ A$XbxąNx-x΍B*zDc NQ8]O`KFV0ט?L8AHt@`*5bZm(#lR/Z +Y1+:D"#\\~_P^ #m01X'tj8a.y(BJG?}EAocOdq_]4$/"kPv\2"_)y`4t6JʊHq9:sbĚYQcn!fJפֲ͕^Gt󘁓t)(U܅{sLy{j46*z%]脉/HB e0IBogS720~ ߐ-ij]nlB%5t.><7m^Ke Ԩfv66zC }yK(O*"^iBe߲SyWrwnʴ{*;a__^ #ݬ&&fT4_)jmeIVR, kͰoV jY'ϕ?\ ;КܧgJcF Y"֧vGA{H4B>ͭt ?5B=80R}?/Sn1|Jyڄ@Q%RESΪ5i+w^xfgfj9j{,d{~`a8jt;vFԙ/Tc!=ʊb)aBYВlOl,!|0bdpttj}}yHRM0-dv89-)Ě-Ƽ<͂9TJÏg:Lu7Rz"|nJ6ugE',.%8PVȫ7.5W;fwEo5 hX-Z: P]#+ipܔ#8I/VlI([->n8r3ْu][Gj{O#hOVnF}WHKd9v\IP19^;KD/]qwrΙ١ySMcNWt09JP9mV|:@mqeʩe#\:<3r/4<' >rgm\y A&Cjwj^zMu9cQjt=Xk2 ^+kI+OW7!TM&Aҥf KA)6'~\& 9Gt[ʹxle͗OfEZekNT~Q^ +[WA_x}aLNNp"bO)p S+ts-L}Q;],im?ׅUx΂ +CCve& Ƕh5UfYKZģ9ɽw,yaS#> _ޒH0d]+WxFpԼEv@܄x<-qE1R+!w@6`x]A-uձaR0v+㾨Z=- 2 7WޑiKv +KQ8bm1"L"쭆ENwؔZE- q0,LEn+@"%z|2=wmNnBCTpzl\vhwK푱Z nLM`6fӠ@kOnU@z.;w`7{If!:%IѩeG>,l>o9iya,PcLQ + TdR195>,{ԌEaeq=ͨHXdvmq.+k7_VZVM9 +Ø pKG*~?)VW(ϷM&e S % +9I@(͂w0_S\.}̖N:Tk0!?צ͚,$> "cQYҤs0)4Iҽ{/&7"b +N+tQ4FЅ\8ȄD :y.8 [ b*{Zrpì»u}8̑` +D 73Am$z 0׊XRJGKQ92m FB!lBtzP`0ti9i]0R@gG>럡#(*G*G0>='lu: +T(6ͣ;"l(dL.h'H=H,Pe؊.m ޾+: +}|V[rxĤ[!m{c wr*^/>;y1*AKG-(]b㤪گL[d<]ɫ\?fnIVM$t2 >q?к`?0RM4=_mU,r8;5-Y-m Ӫ?ԸTcne|w=ozu1A=NUI߷j?宒if,RiթC^=7uԖU\֧E܀۳׻$CUmo6_q(RH2xIcMb6t6$ˣC@%ۉ/wϽ=w:p9fYLY4H:t 0@Os<ԸfiŔ;H>Yp!#8gV>8<1>2r)Z8Z02ܠ6"0CpRpE[paRH ʻז"CEBM1':#\ࢴf|g Tqp>y҅2)"fHe8"(*ǥ2Xܤ_J?*Ww$($# +׷h4 T2@ӁќYs&:Rn3pJ\ˤuqS'im"#}qULy6Zd(?Y/C*13`"b2K: n|XW\ĎpmI,]M_ާ%TE1 ĸ/PRB;a3sl?{TYm-N="]U~|cR)JE&nHI棯Y/_,eN-( ;yibĄ#x^,97\گ z=\-M,Nj_bOTڻ(\k|"ZzjƬv:OO;h~06}l3[6YRF5rPuFOM8sGj.׏\S*N,>mҖ3'y.4!8`+ިќd6/#zPd>j)qgujޙbNRjAyı8Gb/So6VoF ~_$rKIlI1A% J[wڑgO(+^;}$O>{_kt8Bxc2MOFpa4b %ܕjC#h,lU;*h4_ :CY?-<ʐEV!) FʼX]) ^kwуV9&PfiEYB `hz{u:@5ĤD-2XTVI ȳ.'mS + Jtsl42y ,9g3vce[An{wNUp~gQ~QR*JA+Mk2p{`zrt rjB0{1>*Xְ8Kh.DXǂyk2k #H)/O,T 6yە՚ZL\ s0}i'gPi~er" +9N&Yߝ:¼|z@1q\aɮFkikeu@B蓅G0w7QsİN> 8r[ yG0PꠊOo; 8OQP"ތ͟%N!ݽAH3tѿMQKBAWGIzM+K wC;Wǽe83ߙ3gfg8 +z85/^* Tˑ zbȻW %2.Z1yW +E}>e>:p45_/H@sx[!rUӢ$c^Ȳ(:[q{ŶeQXoD`^X6$ hjd6yl1e`}0 v^+`g' Ml> v6QNGq|up}7@p{ o|49qʨx<'LG?ĩ& /(V) +iӵ'ɅB(p9![mSn0 }Wp@ARcMk,@K @t-LNZAd9<7j1̠o^]pSn![R~s +č~ѐKf$n<x`8s4piFd d!'elp PB@twh#DWrZ G~wss*QNBm c8qJxr!D*%dZ"z.&"2G.rb"\!\fŧ{iy`ohh%W,F<3das'#Sӆ!RXy}pdTG$E{MoiT`4Q]ޮ_k:?AmDJZ2yD~pͧȢ7hs-#RZ*"lṱrp,W=ؚ24 hGشAA)ڬG\tw3 +eq7n|| \sKhS&;5*+YAAʍ.[KɄ[~b¬0KC2sUhP+If% As zyÆRNj "Ν +N:75 8*wܘd8N&ʟmWh5B3b5"Mn?cO,ז|zR]o0 |/^KWdXa(f/(2 S$E n~`z'W;(QjAy&%y;c>NFp_+ʃ`+UZču;R!9\A& NWī0L|_ڡu#)AZäV-N% +Z|Th$Lei#XYS(E1Vx(?ha [5pl@AڲoyAD_m"$ >22*2KA$vӲw WiV5 Jt5N0??$ F:ZeGp7NN /ptsa|2\Jkf|p{}vHCl ukV#x2Eȁ̱#S-(i^8# UAߒgjACeҵ:/-VSk[0}s|<"fݺ:[A;,-FN&'To6PdqՎ]gYhـY0"F%[-k1~EݏW8PjA'%:t B1J#('ȃK<*ʺ-U!)\AA gdp +J K$b +i LOn@KAh$FP&^Y3Q0“Mu(U ]/`|>g[D6k(##NH/Jip:;Rǟ?_k\(Z0mU?)W0 `$~[e6Wԓg;0M&A=[:ץG>4iۤ5p΂ y-zB/n:Jt?[,-]j%8 WLtRWmD̚Y$x¬&tJ޼7=m<[o=)JtzrrHѼ’ Q9$Ff3xז>JD09cxcPr[ rKP|X+Qb\jRX9x?fN/N޵FK2-IhBJk‡ aϕPW}uJq3x˖Y h=YڹfRoX.kubi{KY@æ^4QS'7/5:A_`ыob0~y~ݿj鐓tHl_ +Y@bt6/N!,v[ }* 5j4=GN1)R~zK +FmJ(AZ9] c[lB%jљ9ߙwCFR O^INy(uIRJTas\jI̺WEhN!|ܑ瀡N0'c 'td(05բbCcxa=$֐J er+k4@X*Z}_&g|GTx +%8 mqO I*z#$¦t4ׁ\ylz$RQJxne d?pB+ߦߧx6\N1B2wƮLk~'#Z +&weg o'+#v5$Yiۏ;<<.cX +&wsen6TjmSO&L \ ʾ5?EUupzfwQV} S=m/˛`L[XAYll^bώb|n} IZ˷ǐHeL/y2TC]UZSΊ,D!!ry4B/7+X#LxA1w݉J'}s;Y|ca1g'ÚMWfwjza(˳l Ͱ4̲/_Yq+NyUբ=a-q0ITQ"J&{ +uEWHX/q+ +h< |4a-ZR;h<EAO1@<"DcHhbm,mәe1nv<{mv@[}r}5` /T#O z2ߝb`+q(~pQJCjcHTq'Xɿ_Tn0+ I}ܚ8um>:M%"4)+F/(ˎ8@y;%/ߗENI^$?y^D~> R#HvxL&kebGl*qFFTO0HĤ}Y)mM|ۦ\׶NK5ONI˗${}2 +bY +%?MzI'A.t`= +*gcW,gX$pɚB@!k6waBSt^TÙk.laXE3F{vu+ T +Ħz@+)H;ss.XEվoσdRU| n]2XJ_)Y5O"sr]<4?t(4I:G׽6`0ߎhk@f\JQ:ZazdZ.9ӌ UD)1{41 T2Y9'lы"9qFA;8tb az&-zCe/GV<-lЁ &}WG6_YW;qur!M+Ÿ>VxGޞ3._ل݆U.x. Ou_JR !n'p]Zx*ڠi*vޠrtIeA㻸GGrAr*+HM0> Oى+5ZhŖBv2N>4l0JtW ֞~- m=w\>R9,ﯶ&UCp4{'̥o7AJ%`jzt&&u_):8|U*ڷ_u̵3^ioC!?_0*ͺV8ZOuR[o0~ϯ8* ((FVZ+@ڥqNUc[C+CHsiv/T BʉFXͨ]ڽB3^ۃ6,f fPD[1<$ld > `WN3jkhCqɋLAp!›P^|Y~b7$RQ,Q+YYQa)$)ABVT*fwMDlņ(Dxh˫+(2HH{a +Te"AHxZ<1AYCFįhTJ-K@ ?K=S@Qh/&"K+-9MZsJjG헉#u\?G ̛Ն[<] T*?g]v +͵&VnScoWN-X43_ fW}HgFcM?1.$ UEa4N3bl0Y`[]Znl}Ӝ'0kN!V>hn2" V}{w ?Xf9@^f4OOx.]A-VF ]3J([0n0&;" +cOWU+w2vXYe邤iIô5ȵ@U + MF}) 6hƑN+i^5?+a{6;93'p^A6 ÑFjLu{W΂@Fddħ6U(Ŗ0;LV\ I2:gbU8f.N5sٱ|vuvM2w  &S@=찕,}$kt2Aa| +)RTLPOκp"Ls+C6_jsg\4~REߚ=ύvϊHBEA2e2K: 6eӅ  +xj1JCfmA-^ nd?TQB(/vL$벱)V5tTY`BWS0o U+^tj'-4&܏5 E."N%UJ B{Ζr]ռm0p>*Wua_ٝRJ5HG GU6|{nvO ٕ"1[@ߘzv )^@hWV|[ +{WMaЋ~.,w)E:6,0OF<[?̏FM<]*L+_uJs[e;u.?}0"GnW-Gp9cEy>l_MTZC{"ϭOm Ñ'оkw05_Րnd4TM@'xg;@O0O.` o?;lqS"C8Ϡ|%Iܵ>ѮʅmwS($~! ?KJo ϳvfBS*Ѣ}qEoLFD[ v.햬fn6+#j^; []:(e+t: 30^x7'9*W4W2~ E&GL{ &y}5;k͒*zV5r9u&;htě-a$IrL,io5) +exUARk O3, +]*1mt2?_qŌIiо5`KŸC`UB'TcMI&<QxFpjٵG6p`B"(?aOU O.!9@Wb7j6z5>`@ x VutoF +}@bA{k Jhx &)mb@ͤvr!a)ݫS%Q;}) ++<}_oOUD0H #8)N}xg2DMQaz4r# Ȝ;u>.^.qs%*A S,uZ¯`<Xg6lI:euֶ#'wR%Ϡۭ41eگ2_lftkB:~CY0mu$ Pu`NiDxދa}5z]!.^C%_jbz+LuvA.#f]*5 `ɤ +sc6#}:s"> Ug5_Iˌ_Hd(&Y٘EMOB1Ew @\ +Tc416y|CLΝ;2j +Dr}՟`W /T N"cNѳ}Kd\T0yVQ7Cl"QOrT΋YvS܁XæW"TѴ!^( T>!r`{Z-ϛewTL)$ޫv%2lϕ&Җ%eUz;}dbL+rcl (Zc2MoA +|@JR-\Z*q!Mf#&$w4JQ)͞~絧ocD P_(7Iq~R |k@<BY-qǮnvWLA*05LxcÕu#&dJWM=f `)Ur|A`{L{gQxmChap}Y\ΣZc(쵰4"Pv 2kh,mc&,ۍL"qin E {FdC^Jc +VYl $(ߍOQ B̏ X951u0`Є5&-XSXQFR>?8t^~md8jup +5;Jh%5' +[yJ 9dqm6CeiA=䥿P8jFAc\mo6_ +I@/w״1v4`Ҭ5WTI/8"%%J3|/Ù!5Fwx=Fr= ~kp /bLztE{\n@5L\D{зE̻tVA~3k؍#M5b]*0z@p+Rt֛NJPk#Dvj mS F .@6z=1vĥ젱JkcƦe5`1?a'@M>iE%W7~ĄI,Y'G$ܚťi@ԄEF_gN=% +-a3_0zķ)zȽ:{tOX?$Kߒ'DHE'4\p~_wF(gBK7Bo +(7+W&Ͳ3*Cyx-?R(ufcKL٫iX©5 }DhbDtk%oERys*ec/t#JVP6\FA+NNۉpy OinMnSl_rDXsL <=.gg'iּUS]Ӛ-o[S.gTL*JGue`pwbU o`{cz6$5!> B_zb0X2zhsfˠ'lz1+?:NW(/\ ]W=ERtEp~1Ysekhxk/#BKv sJbTV׋:N4ظ[_mS.wX㻴xh$.cܥSWc +F?P-okF8R:?7_ zt`㣃FCbāي*vY~dƌ@1cHM;L& +>jY,0 UcV)8cP:U^XۭXAoN4+HMoJ@G,$H4-M +n3j9DGɇH:,`w `.:rHp qN7_y=8;ٯ !'7|hh M#7|L=L,F?$TCk!h]$SL:K!m@<('yZ$w$ ##/_8Տ8a~!zrՋg hhQ@MqQɢ{cCwҬ{HXTte\=q&˶y[bD+b4tU>[%tՂCyV6ϑ,/!8Q%\;k1pX89"meK:-cl-z*gvYfe=̭HG)̽9ۻ8sȹÅDeZ "rCrx~!vB>̩οmѺw2ʀnuܑ,pǐbf;\1Bxv8_EΤw /M?$IN?Ϧߒԩ * :9[_]ee#)_hg?>.ko뇢:vN57<<;34vm{!K/hE%(H/uADHy-?LO d)&HA$\q[VkI&ɔ7)nKA^Phgd7THF3JhuyB$4[$$g7̹|s"@e]]a,La2*Y"*1t?JoL$TZ LTy Ih| &@.ה̿iBafB_',DBV E)/{Ȝo Do /˳s , '{{IJ \#ɫ;IVKْ6x@%^R^ڣ.yʾ]?J#<ֻ<@A7I*E(oRt깾ֺe"'& %fT=En2#V\9;Y˳=7zJ|6$,Bghgxv5ꉗRS3̩o1 V.db+P>jftzPDG1x Z,+(r̄4$3P|W%"{.ԉYQ߁ex`wȷZңWz~ۤjBOLgk**ohJ śoLIE@Gh4r~t ITqj"CT6$,,=^ "=bJo#~|󋖾=_ W۵,*yzMRn? gH3.d̲\r*aq W#9z3}}kzTQ{4l*O5?]M]=Ya r &SD̈{a3*t=΃ aCƈTpj^|eh;+QBxnM :Ns_jZל߲lN{+6->Hl>fl@>h.,Z$ho nӉQ!]nuy=̿ﳴo1/\7SFEc;g$1rcJ@ C(,[; @h c/^O[?'U5<$6$E +f4}PELӆEP!ܸYkK^,|Ɲh-^}[4u + YlhDcb)hda!GUzʕUƋ|xԤ8uӑ_yjuDZ[e pBO̕5tuX B^[9҈ɯl%HևMn sL~Tͤ"]A!C< ̈ 3Pw[>HY7Q3e +i74b.3# pPh_{|es [T=.庳/f y<6e!6؃"m*%jQu +UdISb-e!fŚ#RxnOɿ壳~ݶ Pn%Iw^i lkFnμؒLIOO.bQ y?]3(R/T}̪&Mž7֛z7@ 0]O:o8^ d2mx|Xf'H^=Dncn1MS`2`t}&0&K8#0,&B ]6 ekRi^FpΌX-YAݶXh2Uj5H +6uۧ^P=u=y߭"j蛂JvR&9kiDŽ:",C^״fc((39050iU}0zpT 1l\4ި[ׅbwG 8V̌H_hìX +L{|mVl#YYlQYrْ5$ :#A' !͈DTxg?t3E)UC:7@NP'6r&;RL~?[)7, (^R:I4LZxSwU(p*͟iVS3}uh`c*>6Mil?n^6E*٩iL!Wg!tQVT WA`eO#xb&}qsljho]8t:ߚ^gPzMae{"AOoh07Y7˧Wyo}15c0#,yXƴ< F +@Žvz,$ѭb;s\ƜC:T&]7OO@&=Ѱ5E 4P06#]nvj1f2/y%l:gWKQпtqL ^cfty:`L|0$A+EH:(PlHEO%0\&y:0$ N' + +T{nz=S +;Xh#KmSJ;_5nk`\bPX*Iyqv/u +KSЋ`i9@' +#&be)-9ӆ8rX?n~O ?=hf֯f[M:dq='oxƀВ=G+46~(xT=o0+5t)dj!)L%"")ߑ2Xrt#5mZ^-WR7÷v`SѻsѳumTu2PW+ϧ*6LJ@.AcK)"Pr \(@KXQ1 !GAG6?84w 9r;L~/s^(:WCybjz2_1ݤ#!,G KZ2{d}5?sHZaǻKxZ`e"_9cbп'1? |eSl3,]~^a0/6dv'Zn6E 1cK[!ɜc,ž0;/(*J-)-SRPPPPN,/H+H//JWA&敔%@Js3KRSJչbsHMWOK*(PPRVp wqsuUVVRPSN@+|KVzBP +B@RilO-kwK $@AO{oތ N9$#8 ƟF.y~6;]Ly7?l9lv lY++ ;Euk2dQwf6@NYF`jCh㎢ZuӴ&MvS=w>{Dߏ*ZaeT-9@bp Vud#2khH*eON~=wPF&`WVR)l!P,4d*@JjP m%ZC Ec#H'J2fY~'p5ͧU/?e ++j=Ssr~ul*:մF:Žu C;M!NBhXUtBCp^ m B[~^AԚnz`{mgD35s6+-vYҽG#dĢ+p?#Ԏhvq -+#>a€i~?s<]Qk7Zj)Jn +eЇ:'$]¬MGPGv;/T\U_]IHr*A؍]0 hHZ&+nt03z7>0:GBMB \ No newline at end of file diff --git a/tools/phpstan b/tools/phpstan deleted file mode 100755 index 07b26c7..0000000 Binary files a/tools/phpstan and /dev/null differ diff --git a/tools/phpstan b/tools/phpstan new file mode 120000 index 0000000..5e15af4 --- /dev/null +++ b/tools/phpstan @@ -0,0 +1 @@ +.phpstan/vendor/bin/phpstan \ No newline at end of file