Compare commits
119 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
642e2bbda7 | |
|
|
1ff285c572 | |
|
|
60d658c5b5 | |
|
|
58c91cd888 | |
|
|
792150d027 | |
|
|
b5aff00a52 | |
|
|
067b4fde58 | |
|
|
b9ec7d93fe | |
|
|
5f9dc382bf | |
|
|
7e531fa24d | |
|
|
4f08958e05 | |
|
|
d3ff5fec0e | |
|
|
d3cc5ab80b | |
|
|
2f54d7b25f | |
|
|
3f33ba4c67 | |
|
|
cfaa77c750 | |
|
|
020a76a804 | |
|
|
80e37628a9 | |
|
|
4864aa2dac | |
|
|
33eb9b136b | |
|
|
17e1967798 | |
|
|
2187505ead | |
|
|
38122ad449 | |
|
|
6218f433e8 | |
|
|
1b6cb53e74 | |
|
|
380e0104f7 | |
|
|
f202473cb1 | |
|
|
39cb211d31 | |
|
|
862a07a930 | |
|
|
183b82cc23 | |
|
|
7f308faee2 | |
|
|
a2fd7eabd9 | |
|
|
94a4f8ce9b | |
|
|
40dc091991 | |
|
|
4f8268a02a | |
|
|
8839e93dba | |
|
|
a97aec49ff | |
|
|
e43b042d47 | |
|
|
830d67d80f | |
|
|
570a3312f9 | |
|
|
94572e37dc | |
|
|
b54c4ddf23 | |
|
|
fb73a7e90f | |
|
|
c0d29a945f | |
|
|
78d76615cd | |
|
|
1f94fb33ae | |
|
|
4a11e3aea7 | |
|
|
c3c99d6629 | |
|
|
41cdd17bf0 | |
|
|
08b965bdef | |
|
|
7028549592 | |
|
|
9b54f1afab | |
|
|
8baf36f516 | |
|
|
c5e21b5de6 | |
|
|
3825c739b1 | |
|
|
d5f1896389 | |
|
|
99dffe1f0c | |
|
|
de8ee89f26 | |
|
|
59eac409ce | |
|
|
33ad39f854 | |
|
|
cb6a7c4ba1 | |
|
|
4d6693391a | |
|
|
1193484136 | |
|
|
916a6e14f3 | |
|
|
24d760418a | |
|
|
249d162f1a | |
|
|
78c7e55c00 | |
|
|
7e38e043ea | |
|
|
ad1c70765c | |
|
|
e90dba91d9 | |
|
|
baa9a4396f | |
|
|
894227552c | |
|
|
c204fd5a4f | |
|
|
bf162ac591 | |
|
|
b396512ed8 | |
|
|
a26cf06434 | |
|
|
8d0e9c4c32 | |
|
|
46fbf4d175 | |
|
|
150ed9dd55 | |
|
|
b11215d971 | |
|
|
d26874f911 | |
|
|
e8c1311f17 | |
|
|
439f0be9b9 | |
|
|
9cee180ebe | |
|
|
4d141ea52d | |
|
|
40801a527c | |
|
|
3367e85396 | |
|
|
9daa802412 | |
|
|
2308f4f1ce | |
|
|
dc31f1f8e0 | |
|
|
abc7f44aa8 | |
|
|
968ad065d7 | |
|
|
2f6cbc5b83 | |
|
|
e0408a38b0 | |
|
|
a5d484ef4f | |
|
|
69a57cf8d4 | |
|
|
d4d9bba3c5 | |
|
|
c939676885 | |
|
|
674f8e4778 | |
|
|
e15d3843bb | |
|
|
016951ae10 | |
|
|
6eefe702f2 | |
|
|
b759164a8e | |
|
|
5c10a78583 | |
|
|
0ef7d66753 | |
|
|
e935ca93a8 | |
|
|
a7adaa1c3b | |
|
|
7426ded03b | |
|
|
d4c79356c6 | |
|
|
def9f76157 | |
|
|
cb499f5074 | |
|
|
a3bb1f01c1 | |
|
|
515a304e30 | |
|
|
1bd365aa5f | |
|
|
ab05de4ae0 | |
|
|
da1133d8d6 | |
|
|
76432aafc5 | |
|
|
11b6f654a9 | |
|
|
17e38c7a60 |
|
|
@ -8,6 +8,7 @@
|
|||
/infection.json5.dist export-ignore
|
||||
/phpstan.neon export-ignore
|
||||
/phpunit.xml export-ignore
|
||||
/renovate.json export-ignore
|
||||
/tests export-ignore
|
||||
/tools export-ignore
|
||||
/tools/* binary
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
@ -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/exporter/issues/new)
|
||||
* [Send a pull request to fix a bug](https://github.com/sebastianbergmann/exporter/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/exporter/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
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
github: sebastianbergmann
|
||||
liberapay: sebastianbergmann
|
||||
thanks_dev: u/gh/sebastianbergmann
|
||||
tidelift: "packagist/sebastian/exporter"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# https://github.com/marketplace/actions/api-surface-check-for-php
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".editorconfig"
|
||||
- "LICENSE"
|
||||
- "**.md"
|
||||
- "**.png"
|
||||
- "**.svg"
|
||||
- "tests/**"
|
||||
|
||||
name: "API Surface Check"
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: api-surface-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
signal:
|
||||
name: Signal
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: "true"
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# https://github.com/marketplace/actions/api-surface-check-for-php
|
||||
|
||||
on:
|
||||
# zizmor: ignore[dangerous-triggers] workflow_run is required to comment on
|
||||
# PRs from forks; this workflow only runs a pinned action and does not check
|
||||
# out PR-controlled code.
|
||||
workflow_run:
|
||||
workflows: ["API Surface Check"]
|
||||
types: [completed]
|
||||
|
||||
name: "API Surface Comment"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: api-surface-comment-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check-api-surface:
|
||||
name: Check API Surface
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write # required to post the API surface diff comment on the PR
|
||||
steps:
|
||||
- uses: composer/api-surface-check@bba9f1cb940df6e02271b1b6158679ac0b3f39d3 # 1.0.1
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
# https://docs.github.com/en/actions
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
name: CI
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COMPOSER_ROOT_VERSION: 8.2.x-dev
|
||||
PHP_VERSION: 8.4
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
coding-guidelines:
|
||||
name: Coding Guidelines
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use local branch
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [ "$EVENT_NAME" = "pull_request" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
else
|
||||
BRANCH="$REF_NAME"
|
||||
fi
|
||||
git branch -D "$BRANCH" 2>/dev/null || true
|
||||
git branch "$BRANCH" HEAD
|
||||
git checkout "$BRANCH"
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
coverage: none
|
||||
tools: none
|
||||
|
||||
- name: Run PHP-CS-Fixer
|
||||
run: ./tools/php-cs-fixer fix --dry-run --show-progress=dots --using-cache=no --verbose
|
||||
|
||||
static-analysis:
|
||||
name: Static Analysis
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use local branch
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [ "$EVENT_NAME" = "pull_request" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
else
|
||||
BRANCH="$REF_NAME"
|
||||
fi
|
||||
git branch -D "$BRANCH" 2>/dev/null || true
|
||||
git branch "$BRANCH" HEAD
|
||||
git checkout "$BRANCH"
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
coverage: none
|
||||
tools: none
|
||||
|
||||
- name: Install dependencies with Composer
|
||||
run: ./tools/composer update --no-interaction --no-ansi --no-progress
|
||||
|
||||
- name: Run PHPStan
|
||||
run: ./tools/phpstan analyse --no-progress --error-format=github
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- 8.4
|
||||
- 8.5
|
||||
- 8.6
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use local branch
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [ "$EVENT_NAME" = "pull_request" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
else
|
||||
BRANCH="$REF_NAME"
|
||||
fi
|
||||
git branch -D "$BRANCH" 2>/dev/null || true
|
||||
git branch "$BRANCH" HEAD
|
||||
git checkout "$BRANCH"
|
||||
|
||||
- name: Install PHP with extensions
|
||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
coverage: none
|
||||
tools: none
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Composer cache directory
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies with Composer
|
||||
run: ./tools/composer update --no-ansi --no-interaction --no-progress
|
||||
|
||||
- name: Run tests with PHPUnit
|
||||
run: vendor/bin/phpunit
|
||||
|
||||
code-coverage:
|
||||
name: Code Coverage
|
||||
|
||||
needs:
|
||||
- tests
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use local branch
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [ "$EVENT_NAME" = "pull_request" ]; then
|
||||
BRANCH="$HEAD_REF"
|
||||
else
|
||||
BRANCH="$REF_NAME"
|
||||
fi
|
||||
git branch -D "$BRANCH" 2>/dev/null || true
|
||||
git branch "$BRANCH" HEAD
|
||||
git checkout "$BRANCH"
|
||||
|
||||
- name: Install PHP with extensions
|
||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
coverage: xdebug
|
||||
tools: none
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Composer cache directory
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies with Composer
|
||||
run: ./tools/composer update --no-ansi --no-interaction --no-progress
|
||||
|
||||
- name: Collect code coverage with PHPUnit
|
||||
run: vendor/bin/phpunit --log-junit test-results.xml --coverage-openclover=code-coverage.xml
|
||||
|
||||
- name: Upload test results to Codecov.io
|
||||
if: ${{ !cancelled() }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
report_type: test_results
|
||||
disable_search: true
|
||||
files: ./test-results.xml
|
||||
|
||||
- name: Upload code coverage data to Codecov.io
|
||||
if: ${{ !cancelled() }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
report_type: coverage
|
||||
disable_search: true
|
||||
files: ./code-coverage.xml
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
|
||||
|
||||
on:
|
||||
- "pull_request"
|
||||
- "push"
|
||||
|
||||
name: "CI"
|
||||
|
||||
env:
|
||||
COMPOSER_ROOT_VERSION: "6.3.x-dev"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
coding-guidelines:
|
||||
name: Coding Guidelines
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.3
|
||||
coverage: none
|
||||
|
||||
- name: Run PHP-CS-Fixer
|
||||
run: ./tools/php-cs-fixer fix --dry-run --show-progress=dots --using-cache=no --verbose
|
||||
|
||||
static-analysis:
|
||||
name: Static Analysis
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.3
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies with Composer
|
||||
run: ./tools/composer update --no-interaction --no-ansi --no-progress
|
||||
|
||||
- name: Run PHPStan
|
||||
run: ./tools/phpstan analyse --no-progress --error-format=github
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
|
||||
- name: "Install PHP with extensions"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: "xdebug"
|
||||
|
||||
- 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: Upload test results to Codecov.io
|
||||
if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Upload code coverage data to Codecov.io
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
@ -7,6 +7,13 @@ on:
|
|||
|
||||
name: Release
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
|
|
@ -14,14 +21,16 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: write # required to create GitHub releases
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install PHP with extensions
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
|
||||
with:
|
||||
php-version: 8.3
|
||||
coverage: none
|
||||
|
|
@ -29,15 +38,12 @@ jobs:
|
|||
tools: none
|
||||
|
||||
- name: Determine tag
|
||||
run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Parse ChangeLog
|
||||
run: build/scripts/extract-release-notes.php ${{ env.RELEASE_TAG }} > release-notes.md
|
||||
run: build/scripts/extract-release-notes.php "${RELEASE_TAG}" > release-notes.md
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ env.RELEASE_TAG }}
|
||||
name: sebastian/exporter ${{ env.RELEASE_TAG }}
|
||||
bodyFile: release-notes.md
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release create "${RELEASE_TAG}" --title "sebastian/exporter ${RELEASE_TAG}" --notes-file release-notes.md
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phive xmlns="https://phar.io/phive">
|
||||
<phar name="php-cs-fixer" version="^3.71" installed="3.87.2" location="./tools/php-cs-fixer" copy="true"/>
|
||||
<phar name="composer" version="^2.8" installed="2.8.12" location="./tools/composer" copy="true"/>
|
||||
<phar name="php-cs-fixer" version="^3.71" installed="3.95.1" location="./tools/php-cs-fixer" copy="true"/>
|
||||
<phar name="infection" version="^0.29" installed="0.29.14" location="./tools/infection" copy="true"/>
|
||||
<phar name="phpstan" version="^2.1" installed="2.1.28" location="./tools/phpstan" copy="true"/>
|
||||
</phive>
|
||||
|
|
|
|||
78
ChangeLog.md
78
ChangeLog.md
|
|
@ -2,28 +2,82 @@
|
|||
|
||||
All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
|
||||
|
||||
## [6.3.2] - 2025-09-24
|
||||
## [8.2.0] - 2026-08-07
|
||||
|
||||
### Added
|
||||
|
||||
* `ObjectExporter` interface, `ObjectExporterChain`, and `ExportContext` for customizing how objects are exported (an object exporter can be passed to `Exporter`'s constructor and is consulted before the default representation of an object is used)
|
||||
* `Exporter::shortenedExport()` and `Exporter::shortenedRecursiveExport()` use the representation provided by an object exporter as well; it is collapsed to a single line and shortened when it is longer than the configured maximum length
|
||||
* `Exporter::hasCustomRepresentationFor()` for checking whether an object exporter provides the representation for an object
|
||||
|
||||
## [8.1.1] - 2026-07-13
|
||||
|
||||
### Fixed
|
||||
|
||||
* A lone `\n` (LF) followed by a lone `\r` (CR) was incorrectly treated as a single `\n\r` line ending when exporting a string; these are now treated as two separate line endings
|
||||
|
||||
## [8.1.0] - 2026-05-21
|
||||
|
||||
### Changed
|
||||
|
||||
* [#57](https://github.com/sebastianbergmann/exporter/issues/57): Preserve `SplObjectStorage` iterator position instead of resetting to first element
|
||||
* [#91](https://github.com/sebastianbergmann/exporter/pull/91): Make binary string output readable for mostly-printable values
|
||||
|
||||
## [8.0.3] - 2026-05-20
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#90](https://github.com/sebastianbergmann/exporter/issues/90): `Exporter::toArray()` silently drops a private property that is redeclared in a derived class
|
||||
|
||||
## [8.0.2] - 2026-04-15
|
||||
|
||||
* [#74](https://github.com/sebastianbergmann/exporter/pull/74): Int cast warning when exporting large floats
|
||||
|
||||
## [8.0.1] - 2026-04-10
|
||||
|
||||
### Changed
|
||||
|
||||
* Explicitly handle `NAN`, `INF`, and `-INF` in `exportFloat()`
|
||||
|
||||
## [8.0.0] - 2026-02-06
|
||||
|
||||
### Removed
|
||||
|
||||
* This component is no longer supported on PHP 8.3
|
||||
|
||||
## [7.0.3] - 2026-05-20
|
||||
|
||||
### Fixed
|
||||
|
||||
* [#90](https://github.com/sebastianbergmann/exporter/issues/90): `Exporter::toArray()` silently drops a private property that is redeclared in a derived class
|
||||
|
||||
## [7.0.2] - 2025-09-24
|
||||
|
||||
### Changed
|
||||
|
||||
* Suppress `unexpected NAN value was coerced to string` warning triggered on PHP 8.5
|
||||
|
||||
## [6.3.1] - 2025-09-22
|
||||
## [7.0.1] - 2025-09-22
|
||||
|
||||
### Changed
|
||||
|
||||
* Suppress `not representable as an int, cast occurred` warning triggered on PHP 8.5
|
||||
|
||||
## [6.3.0] - 2024-12-05
|
||||
## [7.0.0] - 2025-02-07
|
||||
|
||||
### Added
|
||||
### Removed
|
||||
|
||||
* Optional constructor argument to control maximum string length
|
||||
* This component is no longer supported on PHP 8.2
|
||||
|
||||
### Deprecated
|
||||
|
||||
* Optional argument for `shortenedRecursiveExport()` and `shortenedExport()` to control maximum string length
|
||||
|
||||
[6.3.2]: https://github.com/sebastianbergmann/exporter/compare/6.3.1...6.3.2
|
||||
[6.3.1]: https://github.com/sebastianbergmann/exporter/compare/6.3.0...6.3.1
|
||||
[6.3.0]: https://github.com/sebastianbergmann/exporter/compare/6.2.0...6.3.0
|
||||
[8.2.0]: https://github.com/sebastianbergmann/exporter/compare/8.1.1...main
|
||||
[8.1.1]: https://github.com/sebastianbergmann/exporter/compare/8.1.0...8.1.1
|
||||
[8.1.0]: https://github.com/sebastianbergmann/exporter/compare/8.0.3...8.1.0
|
||||
[8.0.3]: https://github.com/sebastianbergmann/exporter/compare/8.0.2...8.0.3
|
||||
[8.0.2]: https://github.com/sebastianbergmann/exporter/compare/8.0.1...8.0.2
|
||||
[8.0.1]: https://github.com/sebastianbergmann/exporter/compare/8.0.0...8.0.1
|
||||
[8.0.0]: https://github.com/sebastianbergmann/exporter/compare/7.0...8.0.0
|
||||
[7.0.3]: https://github.com/sebastianbergmann/exporter/compare/7.0.2...7.0
|
||||
[7.0.3]: https://github.com/sebastianbergmann/exporter/compare/7.0.2...7.0.3
|
||||
[7.0.2]: https://github.com/sebastianbergmann/exporter/compare/7.0.1...7.0.2
|
||||
[7.0.1]: https://github.com/sebastianbergmann/exporter/compare/7.0.0...7.0.1
|
||||
[7.0.0]: https://github.com/sebastianbergmann/exporter/compare/6.3...7.0.0
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2002-2025, Sebastian Bergmann
|
||||
Copyright (c) 2002-2026, Sebastian Bergmann
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
|
|||
256
README.md
256
README.md
|
|
@ -25,19 +25,19 @@ composer require --dev sebastian/exporter
|
|||
Exporting:
|
||||
|
||||
```php
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
$exporter = new Exporter;
|
||||
|
||||
/*
|
||||
Exception Object &0000000078de0f0d000000002003a261 (
|
||||
'message' => ''
|
||||
'string' => ''
|
||||
'code' => 0
|
||||
'file' => '/home/sebastianbergmann/test.php'
|
||||
'line' => 34
|
||||
'previous' => null
|
||||
Exception Object #4 (
|
||||
'message' => '',
|
||||
'string' => '',
|
||||
'code' => 0,
|
||||
'file' => '/home/sebastianbergmann/test.php',
|
||||
'line' => 34,
|
||||
'previous' => null,
|
||||
)
|
||||
*/
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ print $exporter->export(new Exception);
|
|||
Exporting simple types:
|
||||
|
||||
```php
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
$exporter = new Exporter;
|
||||
|
|
@ -85,64 +85,66 @@ print $exporter->export(chr(0) . chr(1) . chr(2) . chr(3) . chr(4) . chr(5));
|
|||
Exporting complex types:
|
||||
|
||||
```php
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
$exporter = new Exporter;
|
||||
|
||||
/*
|
||||
Array &0 (
|
||||
0 => Array &1 (
|
||||
0 => 1
|
||||
1 => 2
|
||||
2 => 3
|
||||
)
|
||||
1 => Array &2 (
|
||||
0 => ''
|
||||
1 => 0
|
||||
2 => false
|
||||
)
|
||||
)
|
||||
Array &0 [
|
||||
0 => Array &1 [
|
||||
0 => 1,
|
||||
1 => 2,
|
||||
2 => 3,
|
||||
],
|
||||
1 => Array &2 [
|
||||
0 => '',
|
||||
1 => 0,
|
||||
2 => false,
|
||||
],
|
||||
]
|
||||
*/
|
||||
|
||||
print $exporter->export(array(array(1,2,3), array("",0,FALSE)));
|
||||
print $exporter->export([[1, 2, 3], ['', 0, false]]);
|
||||
|
||||
/*
|
||||
Array &0 (
|
||||
'self' => Array &1 (
|
||||
'self' => Array &1
|
||||
)
|
||||
)
|
||||
Array &0 [
|
||||
'self' => Array &1 [
|
||||
'self' => Array &1,
|
||||
],
|
||||
]
|
||||
*/
|
||||
|
||||
$array = array();
|
||||
$array = [];
|
||||
$array['self'] = &$array;
|
||||
|
||||
print $exporter->export($array);
|
||||
|
||||
/*
|
||||
stdClass Object &0000000003a66dcc0000000025e723e2 (
|
||||
'self' => stdClass Object &0000000003a66dcc0000000025e723e2
|
||||
stdClass Object #4 (
|
||||
'self' => stdClass Object #4,
|
||||
)
|
||||
*/
|
||||
|
||||
$obj = new stdClass();
|
||||
$obj = new stdClass;
|
||||
$obj->self = $obj;
|
||||
|
||||
print $exporter->export($obj);
|
||||
```
|
||||
|
||||
Compact exports:
|
||||
|
||||
```php
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
$exporter = new Exporter;
|
||||
|
||||
// Array ()
|
||||
print $exporter->shortenedExport(array());
|
||||
// []
|
||||
print $exporter->shortenedExport([]);
|
||||
|
||||
// Array (...)
|
||||
print $exporter->shortenedExport(array(1,2,3,4,5));
|
||||
// [...]
|
||||
print $exporter->shortenedExport([1, 2, 3, 4, 5]);
|
||||
|
||||
// stdClass Object ()
|
||||
print $exporter->shortenedExport(new stdClass);
|
||||
|
|
@ -150,7 +152,7 @@ print $exporter->shortenedExport(new stdClass);
|
|||
// Exception Object (...)
|
||||
print $exporter->shortenedExport(new Exception);
|
||||
|
||||
// this\nis\na\nsuper\nlong\nstring\nt...\nspace
|
||||
// 'this\nis\na\nsuper\nlong\nstr...nspace'
|
||||
print $exporter->shortenedExport(
|
||||
<<<LONG_STRING
|
||||
this
|
||||
|
|
@ -173,3 +175,181 @@ space
|
|||
LONG_STRING
|
||||
);
|
||||
```
|
||||
|
||||
## Customizing How Objects Are Exported
|
||||
|
||||
By default, an object is exported property by property. Implement the `ObjectExporter` interface to control how objects of a type are represented:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\ExportContext;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
use SebastianBergmann\Exporter\ObjectExporter;
|
||||
|
||||
final class MoneyExporter implements ObjectExporter
|
||||
{
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return $object instanceof Money;
|
||||
}
|
||||
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
assert($object instanceof Money);
|
||||
|
||||
return sprintf('Money (%d %s)', $object->amount(), $object->currency());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An object exporter is registered by passing it to `Exporter`'s constructor. It is consulted before the default representation of an object is used:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
$exporter = new Exporter(objectExporter: new MoneyExporter);
|
||||
|
||||
// Money (1999 EUR)
|
||||
print $exporter->export(new Money(1999, 'EUR'));
|
||||
|
||||
/*
|
||||
stdClass Object #6 (
|
||||
'net' => Money (1999 EUR),
|
||||
'gross' => Money (2379 EUR),
|
||||
)
|
||||
*/
|
||||
|
||||
$price = new stdClass;
|
||||
$price->net = new Money(1999, 'EUR');
|
||||
$price->gross = new Money(2379, 'EUR');
|
||||
|
||||
print $exporter->export($price);
|
||||
```
|
||||
|
||||
`ObjectExporterChain` composes multiple object exporters into a single one. They are consulted in the order in which they are composed into the chain and the first one whose `handles()` method returns `true` is asked for the representation:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
use SebastianBergmann\Exporter\ObjectExporterChain;
|
||||
|
||||
$exporter = new Exporter(
|
||||
objectExporter: new ObjectExporterChain(
|
||||
[
|
||||
new BasketExporter,
|
||||
new MoneyExporter,
|
||||
],
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
Enums are objects and are therefore passed to the object exporters as well. When no object exporter handles an object, the default representation is used for it.
|
||||
|
||||
### Repeated Occurrences
|
||||
|
||||
When the same object occurs more than once, the default representation is only used for the first occurrence; subsequent occurrences are replaced with a reference to the object:
|
||||
|
||||
```php
|
||||
/*
|
||||
Array &0 [
|
||||
0 => Money Object #8 (
|
||||
'amount' => 1999,
|
||||
'currency' => 'EUR',
|
||||
),
|
||||
1 => Money Object #8,
|
||||
]
|
||||
*/
|
||||
|
||||
$money = new Money(1999, 'EUR');
|
||||
|
||||
print (new Exporter)->export([$money, $money]);
|
||||
```
|
||||
|
||||
An object exporter is responsible for the entire representation of the object it handles. Every occurrence of such an object is therefore exported by it:
|
||||
|
||||
```php
|
||||
/*
|
||||
Array &0 [
|
||||
0 => Money (1999 EUR),
|
||||
1 => Money (1999 EUR),
|
||||
]
|
||||
*/
|
||||
|
||||
print $exporter->export([$money, $money]);
|
||||
```
|
||||
|
||||
The exception is an object that is (indirectly) nested in itself: it cannot be exported this way without recursing infinitely and is replaced with a reference to the object.
|
||||
|
||||
### Exporting Nested Values
|
||||
|
||||
An object exporter may use the `Exporter` it is given to export values that are nested in the object it handles. The `ExportContext` it is given must be passed on when it does. Otherwise, the export of these nested values starts over with an empty context and, for instance, assigns references to arrays that are already in use elsewhere in the same export:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\ExportContext;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
use SebastianBergmann\Exporter\ObjectExporter;
|
||||
|
||||
final class BasketExporter implements ObjectExporter
|
||||
{
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return $object instanceof Basket;
|
||||
}
|
||||
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
assert($object instanceof Basket);
|
||||
|
||||
return 'Basket ' . $exporter->export($object->items(), $indentation, $context);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
/*
|
||||
Array &0 [
|
||||
'basket' => Basket Array &1 [
|
||||
0 => Money (1999 EUR),
|
||||
],
|
||||
]
|
||||
*/
|
||||
|
||||
print $exporter->export(['basket' => new Basket([new Money(1999, 'EUR')])]);
|
||||
```
|
||||
|
||||
### Compact Exports
|
||||
|
||||
`Exporter::shortenedExport()` and `Exporter::shortenedRecursiveExport()` consult object exporters as well. The representation an object exporter provides is collapsed to a single line and shortened when it is longer than the configured maximum length:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
$exporter = new Exporter(objectExporter: new MoneyExporter);
|
||||
|
||||
// Money (1999 EUR)
|
||||
print $exporter->shortenedExport(new Money(1999, 'EUR'));
|
||||
```
|
||||
|
||||
Bear in mind that these representations are used where a short, single-line, and stable string is required. The representation of a data set is built using `Exporter::shortenedRecursiveExport()`, for instance, and is part of the name of a test that uses a data provider. An object exporter should therefore provide a representation that is compact and that does not change from one export to the next.
|
||||
|
||||
### Custom Representations
|
||||
|
||||
`Exporter::hasCustomRepresentationFor()` tells whether an object exporter provides the representation for an object. This is meant for code that renders objects itself and wants to use the representation an object exporter provides when there is one:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
$exporter = new Exporter(objectExporter: new MoneyExporter);
|
||||
|
||||
// true
|
||||
var_dump($exporter->hasCustomRepresentationFor(new Money(1999, 'EUR')));
|
||||
|
||||
// false
|
||||
var_dump($exporter->hasCustomRepresentationFor(new stdClass));
|
||||
```
|
||||
|
||||
Bear in mind that an object that is (indirectly) nested in itself is replaced with a reference to the object instead of being exported by an object exporter again.
|
||||
|
|
|
|||
32
SECURITY.md
32
SECURITY.md
|
|
@ -1,30 +1,10 @@
|
|||
# Security Policy
|
||||
|
||||
If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure.
|
||||
The library that is developed in this repository is a first-party dependency of [PHPUnit](https://github.com/sebastianbergmann/phpunit).
|
||||
[PHPUnit's Security Policy](https://github.com/sebastianbergmann/phpunit/security/policy) applies to this library as well.
|
||||
|
||||
If you believe you have found a security vulnerability in this library, please report it to me through coordinated disclosure.
|
||||
|
||||
Use my [PGP key](https://sebastian-bergmann.de/gpg.asc) for encrypted email, for example when your report includes proof-of-concept exploits against third-party systems.
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
|
||||
|
||||
Instead, please email `sebastian@phpunit.de`.
|
||||
|
||||
Please include as much of the information listed below as you can to help us better understand and resolve the issue:
|
||||
|
||||
* The type of issue
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
## Web Context
|
||||
|
||||
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.
|
||||
|
||||
The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes.
|
||||
|
||||
If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context.
|
||||
|
||||
Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes.
|
||||
|
||||
|
|
|
|||
28
build.xml
28
build.xml
|
|
@ -17,10 +17,34 @@
|
|||
</target>
|
||||
|
||||
<target name="update-tools">
|
||||
<exec executable="phive" taskname="phive">
|
||||
<exec executable="${basedir}/tools/phive">
|
||||
<arg value="--no-progress"/>
|
||||
<arg value="self-update"/>
|
||||
</exec>
|
||||
|
||||
<exec executable="${basedir}/tools/phive">
|
||||
<arg value="--no-progress"/>
|
||||
<arg value="update"/>
|
||||
<arg value="--force-accept-unsigned"/>
|
||||
</exec>
|
||||
|
||||
<exec executable="${basedir}/tools/composer" taskname="composer">
|
||||
<arg value="self-update"/>
|
||||
<arg value="--no-interaction"/>
|
||||
<arg value="--no-progress"/>
|
||||
<arg value="--no-ansi"/>
|
||||
</exec>
|
||||
|
||||
<exec executable="${basedir}/tools/composer" dir="${basedir}/tools/.phpstan" taskname="composer">
|
||||
<arg value="update"/>
|
||||
<arg value="--no-interaction"/>
|
||||
<arg value="--no-progress"/>
|
||||
<arg value="--no-ansi"/>
|
||||
</exec>
|
||||
|
||||
<exec executable="${basedir}/tools/composer" dir="${basedir}/tools/.phpstan" taskname="composer">
|
||||
<arg value="bump"/>
|
||||
<arg value="--no-interaction"/>
|
||||
<arg value="--no-ansi"/>
|
||||
</exec>
|
||||
</target>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -32,19 +32,20 @@
|
|||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.2.0"
|
||||
"php": "8.4.1"
|
||||
},
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"php": ">=8.4",
|
||||
"ext-mbstring": "*",
|
||||
"sebastian/recursion-context": "^6.0"
|
||||
"sebastian/recursion-context": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^11.3"
|
||||
"phpunit/phpunit": "^13.3"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
|
|
@ -58,7 +59,7 @@
|
|||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "6.3-dev"
|
||||
"dev-main": "8.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
phpstan.neon
43
phpstan.neon
|
|
@ -3,3 +3,46 @@ parameters:
|
|||
paths:
|
||||
- src
|
||||
- tests
|
||||
|
||||
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
|
||||
strictArrayFilter: 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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
executionOrder="depends,defects"
|
||||
shortenArraysForExportThreshold="10"
|
||||
requireCoverageMetadata="true"
|
||||
beStrictAboutCoverageMetadata="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
"helpers:pinGitHubActionDigestsToSemver",
|
||||
":configMigration",
|
||||
":maintainLockFilesWeekly"
|
||||
],
|
||||
"timezone": "Europe/Berlin",
|
||||
"baseBranchPatterns": [
|
||||
"main",
|
||||
"/^\\d+\\.\\d+$/"
|
||||
],
|
||||
"rebaseWhen": "conflicted",
|
||||
"prHourlyLimit": 2,
|
||||
"prConcurrentLimit": 10,
|
||||
"dependencyDashboard": true,
|
||||
"labels": ["type/dependencies"],
|
||||
"platformAutomerge": true,
|
||||
"osvVulnerabilityAlerts": true,
|
||||
"dependencyDashboardOSVVulnerabilitySummary": "all",
|
||||
"vulnerabilityAlerts": {
|
||||
"enabled": true,
|
||||
"labels": ["type/security", "type/dependencies"]
|
||||
},
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"automerge": true,
|
||||
"schedule": ["before 6am on monday"]
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Group GitHub Actions updates per branch into one PR, automerge",
|
||||
"matchManagers": ["github-actions"],
|
||||
"groupName": "github-actions",
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Automerge minor and patch updates after they have settled",
|
||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Do not update the PHP platform requirement in composer.json",
|
||||
"matchManagers": ["composer"],
|
||||
"matchDepNames": ["php"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Do not suggest major updates for Composer dependencies",
|
||||
"matchManagers": ["composer"],
|
||||
"matchUpdateTypes": ["major"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Bump composer.json ranges when updating dependencies",
|
||||
"matchManagers": ["composer"],
|
||||
"rangeStrategy": "bump"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use SebastianBergmann\RecursionContext\Context as RecursionContext;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* Carries the state that is shared by all steps of a single export operation.
|
||||
*
|
||||
* An implementation of ObjectExporter that exports values nested in the object
|
||||
* it handles must pass the instance of this class it is given to the Exporter
|
||||
* it delegates to. Otherwise, the export of these nested values starts over
|
||||
* with an empty context and, for instance, assigns references to arrays that
|
||||
* are already in use elsewhere in the same export.
|
||||
*/
|
||||
final class ExportContext
|
||||
{
|
||||
private RecursionContext $recursionContext;
|
||||
|
||||
/**
|
||||
* @var SplObjectStorage<object, null>
|
||||
*/
|
||||
private SplObjectStorage $exportedByObjectExporter;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->recursionContext = new RecursionContext;
|
||||
$this->exportedByObjectExporter = new SplObjectStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of array|object
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @param-out T $value
|
||||
*/
|
||||
public function add(array|object &$value): int
|
||||
{
|
||||
return $this->recursionContext->add($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of array|object
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @param-out T $value
|
||||
*/
|
||||
public function contains(array|object &$value): false|int
|
||||
{
|
||||
return $this->recursionContext->contains($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the export of an object by an ObjectExporter is in progress.
|
||||
*
|
||||
* This is the case when the object is (indirectly) nested in itself.
|
||||
*/
|
||||
public function isBeingExportedByObjectExporter(object $object): bool
|
||||
{
|
||||
return $this->exportedByObjectExporter->offsetExists($object);
|
||||
}
|
||||
|
||||
public function beginExportByObjectExporter(object $object): void
|
||||
{
|
||||
$this->exportedByObjectExporter->offsetSet($object);
|
||||
}
|
||||
|
||||
public function endExportByObjectExporter(object $object): void
|
||||
{
|
||||
$this->exportedByObjectExporter->offsetUnset($object);
|
||||
}
|
||||
}
|
||||
336
src/Exporter.php
336
src/Exporter.php
|
|
@ -21,16 +21,24 @@ use function ini_set;
|
|||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_float;
|
||||
use function is_infinite;
|
||||
use function is_nan;
|
||||
use function is_object;
|
||||
use function is_resource;
|
||||
use function is_string;
|
||||
use function mb_strlen;
|
||||
use function mb_substr;
|
||||
use function ord;
|
||||
use function preg_match;
|
||||
use function preg_match_all;
|
||||
use function preg_replace_callback;
|
||||
use function spl_object_id;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_repeat;
|
||||
use function str_replace;
|
||||
use function strlen;
|
||||
use function strpbrk;
|
||||
use function strtr;
|
||||
use function var_export;
|
||||
use BackedEnum;
|
||||
|
|
@ -53,15 +61,17 @@ final readonly class Exporter
|
|||
* @var positive-int
|
||||
*/
|
||||
private int $maxLengthForStrings;
|
||||
private ?ObjectExporter $objectExporter;
|
||||
|
||||
/**
|
||||
* @param non-negative-int $shortenArraysLongerThan
|
||||
* @param positive-int $maxLengthForStrings
|
||||
*/
|
||||
public function __construct(int $shortenArraysLongerThan = 0, int $maxLengthForStrings = 40)
|
||||
public function __construct(int $shortenArraysLongerThan = 0, int $maxLengthForStrings = 40, ?ObjectExporter $objectExporter = null)
|
||||
{
|
||||
$this->shortenArraysLongerThan = $shortenArraysLongerThan;
|
||||
$this->maxLengthForStrings = $maxLengthForStrings;
|
||||
$this->objectExporter = $objectExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -76,15 +86,23 @@ final readonly class Exporter
|
|||
* - Strings are always quoted with single quotes
|
||||
* - Carriage returns and newlines are normalized to \n
|
||||
* - Recursion and repeated rendering is treated properly
|
||||
*
|
||||
* An implementation of ObjectExporter must pass the ExportContext it is
|
||||
* given to this method when it exports values that are nested in the
|
||||
* object it handles.
|
||||
*
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
public function export(mixed $value, int $indentation = 0): string
|
||||
public function export(mixed $value, int $indentation = 0, ?ExportContext $context = null): string
|
||||
{
|
||||
return $this->recursiveExport($value, $indentation);
|
||||
return $this->recursiveExport($value, $indentation, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @param positive-int $maxLengthForStrings
|
||||
*
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
public function shortenedRecursiveExport(array &$data, int $maxLengthForStrings = 40, ?RecursionContext $processed = null): string
|
||||
{
|
||||
|
|
@ -92,7 +110,7 @@ final readonly class Exporter
|
|||
$maxLengthForStrings = $this->maxLengthForStrings;
|
||||
}
|
||||
|
||||
if (!$processed) {
|
||||
if ($processed === null) {
|
||||
$processed = new RecursionContext;
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +136,13 @@ final readonly class Exporter
|
|||
* Newlines are replaced by the visible string '\n'.
|
||||
* Contents of arrays and objects (if any) are replaced by '...'.
|
||||
*
|
||||
* The representation a custom object exporter provides for an object is
|
||||
* used, but it is collapsed to a single line and shortened when it is
|
||||
* longer than $maxLengthForStrings.
|
||||
*
|
||||
* @param positive-int $maxLengthForStrings
|
||||
*
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
public function shortenedExport(mixed $value, int $maxLengthForStrings = 40): string
|
||||
{
|
||||
|
|
@ -127,13 +151,20 @@ final readonly class Exporter
|
|||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$string = str_replace("\n", '', $this->exportString($value));
|
||||
return $this->shorten($this->exportString($value), $maxLengthForStrings);
|
||||
}
|
||||
|
||||
if (mb_strlen($string) > $maxLengthForStrings) {
|
||||
return mb_substr($string, 0, $maxLengthForStrings - 10) . '...' . mb_substr($string, -7);
|
||||
}
|
||||
if ($this->objectExporter !== null &&
|
||||
is_object($value) &&
|
||||
$this->objectExporter->handles($value)) {
|
||||
$context = new ExportContext;
|
||||
|
||||
return $string;
|
||||
$context->beginExportByObjectExporter($value);
|
||||
|
||||
return $this->shorten(
|
||||
$this->objectExporter->export($value, $this, 0, $context),
|
||||
$maxLengthForStrings,
|
||||
);
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
|
|
@ -171,6 +202,26 @@ final readonly class Exporter
|
|||
return $this->export($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a custom object exporter provides the representation
|
||||
* for an object.
|
||||
*
|
||||
* This is intended for code that renders objects itself and wants to use
|
||||
* the representation a custom object exporter provides when there is one.
|
||||
*
|
||||
* Bear in mind that an object that is (indirectly) nested in itself is
|
||||
* replaced with a reference to the object instead of being exported by a
|
||||
* custom object exporter again.
|
||||
*/
|
||||
public function hasCustomRepresentationFor(object $object): bool
|
||||
{
|
||||
if ($this->objectExporter === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->objectExporter->handles($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to an array containing all of its private, protected
|
||||
* and public properties.
|
||||
|
|
@ -183,29 +234,43 @@ final readonly class Exporter
|
|||
return (array) $value;
|
||||
}
|
||||
|
||||
$array = [];
|
||||
$properties = (array) $value;
|
||||
$shadowed = $this->shadowedPropertyNames($properties);
|
||||
$array = [];
|
||||
|
||||
foreach ($properties as $key => $val) {
|
||||
$key = (string) $key;
|
||||
|
||||
foreach ((array) $value as $key => $val) {
|
||||
// Exception traces commonly reference hundreds to thousands of
|
||||
// objects currently loaded in memory. Including them in the result
|
||||
// has a severe negative performance impact.
|
||||
if ("\0Error\0trace" === $key || "\0Exception\0trace" === $key) {
|
||||
if ($key === "\0Error\0trace" || $key === "\0Exception\0trace") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// properties are transformed to keys in the following way:
|
||||
// private $propertyName => "\0ClassName\0propertyName"
|
||||
// protected $propertyName => "\0*\0propertyName"
|
||||
// public $propertyName => "propertyName"
|
||||
if (preg_match('/\0.+\0(.+)/', (string) $key, $matches)) {
|
||||
$key = $matches[1];
|
||||
}
|
||||
|
||||
// See https://github.com/php/php-src/commit/5721132
|
||||
if ($key === "\0gcdata") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Properties are transformed to keys in the following way:
|
||||
// private $propertyName => "\0DeclaringClassName\0propertyName"
|
||||
// protected $propertyName => "\0*\0propertyName"
|
||||
// public $propertyName => "propertyName"
|
||||
//
|
||||
// A private property that is redeclared in a derived class and the
|
||||
// private property of the same name that it shadows both exist,
|
||||
// independently of each other. To keep one from overwriting the
|
||||
// other, the name of a shadowed private property is prefixed with
|
||||
// the name of the class that declares it.
|
||||
if (preg_match('/^\0([^\0]+)\0([^\0]+)$/', $key, $matches) === 1) {
|
||||
if ($matches[1] !== '*' && isset($shadowed[$matches[2]])) {
|
||||
$key = $matches[1] . '::' . $matches[2];
|
||||
} else {
|
||||
$key = $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
$array[$key] = $val;
|
||||
}
|
||||
|
||||
|
|
@ -213,6 +278,12 @@ final readonly class Exporter
|
|||
// above (fast) mechanism nor with reflection in Zend.
|
||||
// Format the output similarly to print_r() in this case
|
||||
if ($value instanceof SplObjectStorage) {
|
||||
$key = null;
|
||||
|
||||
if ($value->valid()) {
|
||||
$key = $value->key();
|
||||
}
|
||||
|
||||
foreach ($value as $_value) {
|
||||
$array['Object #' . spl_object_id($_value)] = [
|
||||
'obj' => $_value,
|
||||
|
|
@ -220,7 +291,9 @@ final readonly class Exporter
|
|||
];
|
||||
}
|
||||
|
||||
$value->rewind();
|
||||
if ($key !== null) {
|
||||
$value->seek($key);
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
|
|
@ -236,15 +309,54 @@ final readonly class Exporter
|
|||
|
||||
if (!$value instanceof stdClass) {
|
||||
// using ReflectionClass prevents initialization of potential lazy objects
|
||||
return count((new ReflectionClass($value))->getProperties());
|
||||
return count(new ReflectionClass($value)->getProperties());
|
||||
}
|
||||
|
||||
return count((new ReflectionObject($value))->getProperties());
|
||||
return count(new ReflectionObject($value)->getProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, as keys of the returned array, the names of properties that
|
||||
* are declared more than once in the inheritance chain of an object.
|
||||
*
|
||||
* This can only happen when a derived class redeclares a private property
|
||||
* that one of its parent classes also declares.
|
||||
*
|
||||
* @param array<array-key, mixed> $properties
|
||||
*
|
||||
* @return array<string, true>
|
||||
*/
|
||||
private function shadowedPropertyNames(array $properties): array
|
||||
{
|
||||
$seen = [];
|
||||
$shadowed = [];
|
||||
|
||||
foreach ($properties as $key => $unused) {
|
||||
$key = (string) $key;
|
||||
|
||||
if ($key === "\0Error\0trace" || $key === "\0Exception\0trace" || $key === "\0gcdata") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/^\0[^\0]+\0([^\0]+)$/', $key, $matches) === 1) {
|
||||
$key = $matches[1];
|
||||
}
|
||||
|
||||
if (isset($seen[$key])) {
|
||||
$shadowed[$key] = true;
|
||||
}
|
||||
|
||||
$seen[$key] = true;
|
||||
}
|
||||
|
||||
return $shadowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @param positive-int $maxLengthForStrings
|
||||
*
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
private function shortenedCountedRecursiveExport(array &$data, RecursionContext $processed, int &$counter, int $maxLengthForStrings): string
|
||||
{
|
||||
|
|
@ -262,7 +374,7 @@ final readonly class Exporter
|
|||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
assert(is_array($data[$key]) || is_object($data[$key]));
|
||||
assert(isset($data[$key]) && (is_array($data[$key]) || is_object($data[$key])));
|
||||
|
||||
if ($processed->contains($data[$key]) !== false) {
|
||||
$result[] = '*RECURSION*';
|
||||
|
|
@ -281,7 +393,10 @@ final readonly class Exporter
|
|||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
private function recursiveExport(mixed &$value, int $indentation = 0, ?RecursionContext $processed = null): string
|
||||
/**
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
private function recursiveExport(mixed &$value, int $indentation = 0, ?ExportContext $context = null): string
|
||||
{
|
||||
if ($value === null) {
|
||||
return 'null';
|
||||
|
|
@ -302,60 +417,68 @@ final readonly class Exporter
|
|||
if (is_resource($value)) {
|
||||
return sprintf(
|
||||
'resource(%d) of type (%s)',
|
||||
/** @phpstan-ignore cast.useless */
|
||||
(int) $value,
|
||||
get_resource_type($value),
|
||||
);
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return sprintf(
|
||||
'%s Enum #%d (%s, %s)',
|
||||
$value::class,
|
||||
spl_object_id($value),
|
||||
$value->name,
|
||||
$this->export($value->value),
|
||||
);
|
||||
}
|
||||
|
||||
if ($value instanceof UnitEnum) {
|
||||
return sprintf(
|
||||
'%s Enum #%d (%s)',
|
||||
$value::class,
|
||||
spl_object_id($value),
|
||||
$value->name,
|
||||
);
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return $this->exportString($value);
|
||||
}
|
||||
|
||||
if (!$processed) {
|
||||
$processed = new RecursionContext;
|
||||
if ($context === null) {
|
||||
$context = new ExportContext;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return $this->exportArray($value, $processed, $indentation);
|
||||
return $this->exportArray($value, $context, $indentation);
|
||||
}
|
||||
|
||||
if (is_object($value)) {
|
||||
return $this->exportObject($value, $processed, $indentation);
|
||||
return $this->exportObject($value, $context, $indentation);
|
||||
}
|
||||
|
||||
return var_export($value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses a representation to a single line and shortens it when it is
|
||||
* longer than $maxLengthForStrings.
|
||||
*
|
||||
* @param positive-int $maxLengthForStrings
|
||||
*/
|
||||
private function shorten(string $string, int $maxLengthForStrings): string
|
||||
{
|
||||
$string = str_replace(["\r", "\n"], '', $string);
|
||||
|
||||
if (mb_strlen($string) > $maxLengthForStrings) {
|
||||
return mb_substr($string, 0, $maxLengthForStrings - 10) . '...' . mb_substr($string, -7);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function exportFloat(float $value): string
|
||||
{
|
||||
if (is_nan($value)) {
|
||||
return 'NAN';
|
||||
}
|
||||
|
||||
if (is_infinite($value)) {
|
||||
return $value > 0 ? 'INF' : '-INF';
|
||||
}
|
||||
|
||||
$precisionBackup = ini_get('precision');
|
||||
|
||||
ini_set('precision', '-1');
|
||||
|
||||
$valueAsString = @(string) $value;
|
||||
$valueAsString = (string) $value;
|
||||
|
||||
ini_set('precision', $precisionBackup);
|
||||
|
||||
if ((string) @(int) $value === $valueAsString) {
|
||||
// Add '.0' only if decimals and scientific notation are absent.
|
||||
if (strpbrk($valueAsString, '.E') === false) {
|
||||
return $valueAsString . '.0';
|
||||
}
|
||||
|
||||
|
|
@ -365,34 +488,58 @@ final readonly class Exporter
|
|||
private function exportString(string $value): string
|
||||
{
|
||||
// Match for most non-printable chars somewhat taking multibyte chars into account
|
||||
if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) {
|
||||
$unprintableCount = preg_match_all('/[^\x09-\x0d\x1b\x20-\xff]/', $value);
|
||||
|
||||
if ($unprintableCount === false || $unprintableCount === 0) {
|
||||
return "'" .
|
||||
strtr(
|
||||
$value,
|
||||
[
|
||||
"\r\n" => '\r\n' . "\n",
|
||||
"\r" => '\r' . "\n",
|
||||
"\n" => '\n' . "\n",
|
||||
],
|
||||
) .
|
||||
"'";
|
||||
}
|
||||
|
||||
// A NUL byte or a high ratio of unprintable bytes signals truly
|
||||
// binary data; keep the compact hex dump in those cases.
|
||||
if (str_contains($value, "\x00") || ($unprintableCount / strlen($value)) > 0.3) {
|
||||
return 'Binary String: 0x' . bin2hex($value);
|
||||
}
|
||||
|
||||
return "'" .
|
||||
strtr(
|
||||
// Mostly printable: keep printable bytes visible and escape only
|
||||
// the offending ones inline using PHP-style \xNN escapes.
|
||||
return 'Binary String: "' .
|
||||
preg_replace_callback(
|
||||
'/[\x00-\x1f\x7f"\\\\]/',
|
||||
static fn (array $m): string => match ($m[0]) {
|
||||
"\t" => '\t',
|
||||
"\n" => '\n',
|
||||
"\r" => '\r',
|
||||
'"' => '\"',
|
||||
'\\' => '\\\\',
|
||||
default => sprintf('\x%02x', ord($m[0])),
|
||||
},
|
||||
$value,
|
||||
[
|
||||
"\r\n" => '\r\n' . "\n",
|
||||
"\n\r" => '\n\r' . "\n",
|
||||
"\r" => '\r' . "\n",
|
||||
"\n" => '\n' . "\n",
|
||||
],
|
||||
) .
|
||||
"'";
|
||||
'"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $value
|
||||
*
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
private function exportArray(array &$value, RecursionContext $processed, int $indentation): string
|
||||
private function exportArray(array &$value, ExportContext $context, int $indentation): string
|
||||
{
|
||||
if (($key = $processed->contains($value)) !== false) {
|
||||
if (($key = $context->contains($value)) !== false) {
|
||||
return 'Array &' . $key;
|
||||
}
|
||||
|
||||
$array = $value;
|
||||
$key = $processed->add($value);
|
||||
$key = $context->add($value);
|
||||
$values = '';
|
||||
|
||||
if (count($array) > 0) {
|
||||
|
|
@ -405,7 +552,7 @@ final readonly class Exporter
|
|||
$this->recursiveExport($k, $indentation)
|
||||
. ' => ' .
|
||||
/** @phpstan-ignore offsetAccess.invalidOffset */
|
||||
$this->recursiveExport($value[$k], $indentation + 1, $processed)
|
||||
$this->recursiveExport($value[$k], $indentation + 1, $context)
|
||||
. ",\n";
|
||||
}
|
||||
|
||||
|
|
@ -415,15 +562,68 @@ final readonly class Exporter
|
|||
return 'Array &' . (string) $key . ' [' . $values . ']';
|
||||
}
|
||||
|
||||
private function exportObject(object $value, RecursionContext $processed, int $indentation): string
|
||||
/**
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
private function exportObject(object $value, ExportContext $context, int $indentation): string
|
||||
{
|
||||
$class = $value::class;
|
||||
|
||||
if ($processed->contains($value) !== false) {
|
||||
if ($this->objectExporter !== null) {
|
||||
// An object that is (indirectly) nested in itself cannot be
|
||||
// exported by a custom object exporter without recursing
|
||||
// infinitely and is therefore replaced with a reference to the
|
||||
// object.
|
||||
if ($context->isBeingExportedByObjectExporter($value)) {
|
||||
return $class . ' Object #' . spl_object_id($value);
|
||||
}
|
||||
|
||||
// A custom object exporter is responsible for the entire
|
||||
// representation of the object it handles. Therefore, it is asked
|
||||
// for that representation before the recursion context is
|
||||
// consulted: every occurrence of such an object is exported the
|
||||
// same way instead of repeated occurrences being replaced with a
|
||||
// reference to the object.
|
||||
if ($this->objectExporter->handles($value)) {
|
||||
$context->beginExportByObjectExporter($value);
|
||||
|
||||
try {
|
||||
return $this->objectExporter->export($value, $this, $indentation, $context);
|
||||
} finally {
|
||||
$context->endExportByObjectExporter($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enums are handled after a custom object exporter has been consulted
|
||||
// so that the representation of an enum can be customized, but before
|
||||
// the recursion context is consulted because an enum case is a
|
||||
// singleton for which a reference to a previous occurrence would be
|
||||
// less informative than the representation itself.
|
||||
if ($value instanceof BackedEnum) {
|
||||
return sprintf(
|
||||
'%s Enum #%d (%s, %s)',
|
||||
$class,
|
||||
spl_object_id($value),
|
||||
$value->name,
|
||||
$this->export($value->value),
|
||||
);
|
||||
}
|
||||
|
||||
if ($value instanceof UnitEnum) {
|
||||
return sprintf(
|
||||
'%s Enum #%d (%s)',
|
||||
$class,
|
||||
spl_object_id($value),
|
||||
$value->name,
|
||||
);
|
||||
}
|
||||
|
||||
if ($context->contains($value) !== false) {
|
||||
return $class . ' Object #' . spl_object_id($value);
|
||||
}
|
||||
|
||||
$processed->add($value);
|
||||
$context->add($value);
|
||||
|
||||
$array = $this->toArray($value);
|
||||
$buffer = '';
|
||||
|
|
@ -437,7 +637,7 @@ final readonly class Exporter
|
|||
. ' ' .
|
||||
$this->recursiveExport($k, $indentation)
|
||||
. ' => ' .
|
||||
$this->recursiveExport($v, $indentation + 1, $processed)
|
||||
$this->recursiveExport($v, $indentation + 1, $context)
|
||||
. ",\n";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
interface ObjectExporter
|
||||
{
|
||||
public function handles(object $object): bool;
|
||||
|
||||
/**
|
||||
* Exports an object this object exporter handles.
|
||||
*
|
||||
* The ExportContext must be passed on to Exporter::export() when values
|
||||
* that are nested in the object are exported using $exporter.
|
||||
*
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use function array_any;
|
||||
|
||||
final readonly class ObjectExporterChain implements ObjectExporter
|
||||
{
|
||||
/**
|
||||
* @var non-empty-list<ObjectExporter>
|
||||
*/
|
||||
private array $exporters;
|
||||
|
||||
/**
|
||||
* @param non-empty-list<ObjectExporter> $exporters
|
||||
*/
|
||||
public function __construct(array $exporters)
|
||||
{
|
||||
$this->exporters = $exporters;
|
||||
}
|
||||
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return array_any(
|
||||
$this->exporters,
|
||||
static fn (ObjectExporter $exporter) => $exporter->handles($object),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ObjectNotSupportedException
|
||||
*/
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
foreach ($this->exporters as $objectExporter) {
|
||||
if ($objectExporter->handles($object)) {
|
||||
return $objectExporter->export($object, $exporter, $indentation, $context);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ObjectNotSupportedException;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface Exception extends Throwable
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class ObjectNotSupportedException extends RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ use function is_resource;
|
|||
use function is_string;
|
||||
use function mb_internal_encoding;
|
||||
use function mb_language;
|
||||
use function method_exists;
|
||||
use function preg_replace;
|
||||
use function range;
|
||||
use function str_repeat;
|
||||
|
|
@ -29,9 +28,9 @@ use Error;
|
|||
use Exception;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhp;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
|
||||
use PHPUnit\Framework\Attributes\Small;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use SebastianBergmann\RecursionContext\Context;
|
||||
|
|
@ -39,6 +38,8 @@ use SplObjectStorage;
|
|||
use stdClass;
|
||||
|
||||
#[CoversClass(Exporter::class)]
|
||||
#[UsesClass(ExportContext::class)]
|
||||
#[UsesClass(ObjectExporterChain::class)]
|
||||
#[Small]
|
||||
final class ExporterTest extends TestCase
|
||||
{
|
||||
|
|
@ -89,6 +90,7 @@ final class ExporterTest extends TestCase
|
|||
'float 1 - 2 / 3' => [1 - 2 / 3, '0.33333333333333337', 0],
|
||||
'float 5.5E+123' => [5.5E+123, '5.5E+123', 0],
|
||||
'float 5.5E-123' => [5.5E-123, '5.5E-123', 0],
|
||||
'float 9.2233720368547758E+18' => [9.2233720368547758E+18, '9.223372036854776E+18', 0],
|
||||
'float NAN' => [NAN, 'NAN', 0],
|
||||
'float INF' => [INF, 'INF', 0],
|
||||
'float -INF' => [-INF, '-INF', 0],
|
||||
|
|
@ -146,7 +148,9 @@ Array &0 [
|
|||
EOF,
|
||||
2,
|
||||
],
|
||||
// \n\r and \r is converted to \n
|
||||
// A real newline is inserted after each line ending so the
|
||||
// escaped text wraps readably; \r\n stays one break while a lone
|
||||
// \n followed by a lone \r are two separate line endings.
|
||||
'export multiline text' => ["this\nis\na\nvery\nvery\nvery\nvery\nvery\nvery\rlong\n\rtext",
|
||||
<<<'EOF'
|
||||
'this\n
|
||||
|
|
@ -158,8 +162,27 @@ very\n
|
|||
very\n
|
||||
very\n
|
||||
very\r
|
||||
long\n\r
|
||||
long\n
|
||||
\r
|
||||
text'
|
||||
EOF,
|
||||
0,
|
||||
],
|
||||
// A lone \n (Unix) followed by a lone \r (old Mac) are two
|
||||
// distinct line endings and must each produce their own break.
|
||||
'lone newline followed by lone carriage return' => ["a\n\rb",
|
||||
<<<'EOF'
|
||||
'a\n
|
||||
\r
|
||||
b'
|
||||
EOF,
|
||||
0,
|
||||
],
|
||||
// \r\n (Windows) is a single line ending and stays one break.
|
||||
'carriage return followed by newline' => ["a\r\nb",
|
||||
<<<'EOF'
|
||||
'a\r\n
|
||||
b'
|
||||
EOF,
|
||||
0,
|
||||
],
|
||||
|
|
@ -181,7 +204,8 @@ very\n
|
|||
very\n
|
||||
very\n
|
||||
very\r
|
||||
long\n\r
|
||||
long\n
|
||||
\r
|
||||
text',
|
||||
'object' => stdClass Object #%d (
|
||||
'foo' => 'bar',
|
||||
|
|
@ -231,7 +255,7 @@ EOF,
|
|||
0,
|
||||
],
|
||||
[
|
||||
implode('', array_map('chr', range(0x0E, 0x1F))),
|
||||
implode('', array_map(chr(...), range(0x0E, 0x1F))),
|
||||
'Binary String: 0x0e0f101112131415161718191a1b1c1d1e1f',
|
||||
0,
|
||||
],
|
||||
|
|
@ -240,6 +264,41 @@ EOF,
|
|||
'Binary String: 0x0009',
|
||||
0,
|
||||
],
|
||||
'mostly printable string with a single control byte' => [
|
||||
'Hello' . chr(0x01) . 'World',
|
||||
'Binary String: "Hello\x01World"',
|
||||
0,
|
||||
],
|
||||
'mostly printable string with several control bytes' => [
|
||||
'BEGIN' . chr(0x01) . 'DATA' . chr(0x1F) . 'END',
|
||||
'Binary String: "BEGIN\x01DATA\x1fEND"',
|
||||
0,
|
||||
],
|
||||
'mostly printable string with embedded tab' => [
|
||||
'Tab' . chr(0x09) . 'Sep' . chr(0x01),
|
||||
'Binary String: "Tab\tSep\x01"',
|
||||
0,
|
||||
],
|
||||
'mostly printable string with embedded newline' => [
|
||||
'Line1' . chr(0x0A) . 'Line2' . chr(0x01),
|
||||
'Binary String: "Line1\nLine2\x01"',
|
||||
0,
|
||||
],
|
||||
'mostly printable string with embedded carriage return' => [
|
||||
'A' . chr(0x0D) . 'B' . chr(0x01),
|
||||
'Binary String: "A\rB\x01"',
|
||||
0,
|
||||
],
|
||||
'mixed string with embedded double quote and backslash' => [
|
||||
'a"b\\c' . chr(0x07) . 'd',
|
||||
'Binary String: "a\"b\\\\c\x07d"',
|
||||
0,
|
||||
],
|
||||
'mixed string preserves UTF-8 high bytes' => [
|
||||
'café' . chr(0x01),
|
||||
'Binary String: "café\x01"',
|
||||
0,
|
||||
],
|
||||
[
|
||||
'',
|
||||
"''",
|
||||
|
|
@ -351,9 +410,9 @@ EOF,
|
|||
public static function provideNonBinaryMultibyteStrings(): array
|
||||
{
|
||||
return [
|
||||
[implode('', array_map('chr', range(0x09, 0x0D))), 9],
|
||||
[implode('', array_map('chr', range(0x20, 0x7F))), 96],
|
||||
[implode('', array_map('chr', range(0x80, 0xFF))), 128],
|
||||
[implode('', array_map(chr(...), range(0x09, 0x0D))), 9],
|
||||
[implode('', array_map(chr(...), range(0x20, 0x7F))), 96],
|
||||
[implode('', array_map(chr(...), range(0x80, 0xFF))), 128],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -434,7 +493,8 @@ very\n
|
|||
very\n
|
||||
very\n
|
||||
very\r
|
||||
long\n\r
|
||||
long\n
|
||||
\r
|
||||
text',
|
||||
'object' => stdClass Object #%d (
|
||||
'foo' => 'bar',
|
||||
|
|
@ -459,7 +519,8 @@ very\n
|
|||
very\n
|
||||
very\n
|
||||
very\r
|
||||
long\n\r
|
||||
long\n
|
||||
\r
|
||||
text',
|
||||
'object' => stdClass Object #%d,
|
||||
'objectagain' => stdClass Object #%d,
|
||||
|
|
@ -485,7 +546,7 @@ EOF;
|
|||
{
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$this->trimNewline((new Exporter(0, $maxLengthForStrings))->shortenedExport($value)),
|
||||
$this->trimNewline(new Exporter(0, $maxLengthForStrings)->shortenedExport($value)),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
|
|
@ -544,6 +605,19 @@ EOF;
|
|||
$this->assertEquals([], (new Exporter)->toArray((object) $array));
|
||||
}
|
||||
|
||||
public function testPrivatePropertyRedeclaredInDerivedClassIsNotOverwritten(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
[
|
||||
ParentClassWithPrivateProperty::class . '::property' => 'parent value',
|
||||
ChildClassWithPrivateProperty::class . '::property' => 'child value',
|
||||
],
|
||||
(new Exporter)->toArray(
|
||||
new ChildClassWithPrivateProperty('parent value', 'child value'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $value
|
||||
* @param non-negative-int $limit
|
||||
|
|
@ -551,7 +625,7 @@ EOF;
|
|||
#[DataProvider('shortenedRecursiveExportProvider')]
|
||||
public function testShortenedRecursiveExport(array $value, string $expected, int $limit): void
|
||||
{
|
||||
$this->assertEquals($expected, (new Exporter($limit))->shortenedRecursiveExport($value));
|
||||
$this->assertEquals($expected, new Exporter($limit)->shortenedRecursiveExport($value));
|
||||
}
|
||||
|
||||
public function testShortenedRecursiveOccurredRecursion(): void
|
||||
|
|
@ -567,14 +641,10 @@ EOF;
|
|||
$this->assertEquals('*RECURSION*', (new Exporter)->shortenedRecursiveExport($value, processed: $context));
|
||||
}
|
||||
|
||||
#[RequiresPhp('^8.4')]
|
||||
public function testShortenedExportDoesNotInitializeLazyObject(): void
|
||||
{
|
||||
$reflector = new ReflectionClass(ExampleClass::class);
|
||||
|
||||
assert(method_exists($reflector, 'newLazyProxy'));
|
||||
assert(method_exists($reflector, 'isUninitializedLazyObject'));
|
||||
|
||||
$object = $reflector->newLazyProxy(static fn () => new ExampleClass('bar'));
|
||||
|
||||
(new Exporter)->shortenedExport($object, 10);
|
||||
|
|
@ -582,6 +652,428 @@ EOF;
|
|||
$this->assertTrue($reflector->isUninitializedLazyObject($object));
|
||||
}
|
||||
|
||||
public function testObjectCanBeExportedByCustomObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'stdClass (indentation: 0)',
|
||||
$exporter->export(new stdClass),
|
||||
);
|
||||
}
|
||||
|
||||
public function testObjectCanBeExportedByCustomObjectExporterThatIsNotComposedIntoChain(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'stdClass (indentation: 0)',
|
||||
$exporter->export(new stdClass),
|
||||
);
|
||||
}
|
||||
|
||||
public function testObjectNestedInArrayCanBeExportedByCustomObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
<<<'EOT'
|
||||
Array &0 [
|
||||
0 => stdClass (indentation: 1),
|
||||
]
|
||||
EOT,
|
||||
$exporter->export([new stdClass]),
|
||||
);
|
||||
}
|
||||
|
||||
public function testObjectNestedInObjectCanBeExportedByCustomObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesObjectsOfSpecificType(ExampleClass::class),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$object = new stdClass;
|
||||
$object->nested = new ExampleClass('bar');
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
<<<'EOT'
|
||||
stdClass Object #%d (
|
||||
'nested' => SebastianBergmann\Exporter\ExampleClass handled by custom exporter,
|
||||
)
|
||||
EOT,
|
||||
$exporter->export($object),
|
||||
);
|
||||
}
|
||||
|
||||
public function testEveryOccurrenceOfObjectHandledByCustomObjectExporterIsExportedByThatObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$object = new stdClass;
|
||||
|
||||
$this->assertSame(
|
||||
<<<'EOT'
|
||||
Array &0 [
|
||||
0 => stdClass (indentation: 1),
|
||||
1 => stdClass (indentation: 1),
|
||||
]
|
||||
EOT,
|
||||
$exporter->export([$object, $object]),
|
||||
);
|
||||
}
|
||||
|
||||
public function testRepeatedOccurrenceOfObjectNotHandledByCustomObjectExporterIsReplacedWithReferenceToObject(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$object = new stdClass;
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
<<<'EOT'
|
||||
Array &0 [
|
||||
0 => stdClass Object #%d (),
|
||||
1 => stdClass Object #%d,
|
||||
]
|
||||
EOT,
|
||||
$exporter->export([$object, $object]),
|
||||
);
|
||||
}
|
||||
|
||||
public function testDefaultExportIsUsedWhenNoCustomObjectExporterHandlesObject(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
'stdClass Object #%d ()',
|
||||
$exporter->export(new stdClass),
|
||||
);
|
||||
}
|
||||
|
||||
public function testEnumCanBeExportedByCustomObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'SebastianBergmann\Exporter\ExampleEnum (indentation: 0)',
|
||||
$exporter->export(ExampleEnum::Value),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'SebastianBergmann\Exporter\ExampleStringBackedEnum (indentation: 0)',
|
||||
$exporter->export(ExampleStringBackedEnum::Value),
|
||||
);
|
||||
}
|
||||
|
||||
public function testDefaultExportIsUsedForEnumWhenNoCustomObjectExporterHandlesIt(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
'SebastianBergmann\Exporter\ExampleEnum Enum #%d (Value)',
|
||||
$exporter->export(ExampleEnum::Value),
|
||||
);
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
'SebastianBergmann\Exporter\ExampleStringBackedEnum Enum #%d (Value, \'value\')',
|
||||
$exporter->export(ExampleStringBackedEnum::Value),
|
||||
);
|
||||
}
|
||||
|
||||
public function testShortenedExportUsesRepresentationProvidedByCustomObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'stdClass (indentation: 0)',
|
||||
$exporter->shortenedExport(new stdClass),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'SebastianBergmann\Exporter\ExampleEnum (indentation: 0)',
|
||||
$exporter->shortenedExport(ExampleEnum::Value, 80),
|
||||
);
|
||||
}
|
||||
|
||||
public function testShortenedExportCollapsesRepresentationProvidedByCustomObjectExporterToSingleLine(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterThatReturnsGivenRepresentation("first\nsecond"),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'firstsecond',
|
||||
$exporter->shortenedExport(new stdClass),
|
||||
);
|
||||
}
|
||||
|
||||
public function testShortenedExportShortensRepresentationProvidedByCustomObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterThatReturnsGivenRepresentation(str_repeat('a', 41)),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
str_repeat('a', 30) . '...' . str_repeat('a', 7),
|
||||
$exporter->shortenedExport(new stdClass),
|
||||
);
|
||||
}
|
||||
|
||||
public function testShortenedRecursiveExportUsesRepresentationProvidedByCustomObjectExporter(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
);
|
||||
|
||||
$data = [new stdClass, 'foo'];
|
||||
|
||||
$this->assertSame(
|
||||
"stdClass (indentation: 0), 'foo'",
|
||||
$exporter->shortenedRecursiveExport($data),
|
||||
);
|
||||
}
|
||||
|
||||
public function testShortenedExportDoesNotRecurseInfinitelyForObjectNestedInItself(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterThatExportsNestedValues,
|
||||
);
|
||||
|
||||
$node = new Node;
|
||||
$node->children = [$node];
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
'Node(Array &0 [ 0 => SebastianBergmann\Exporter\Node Object #%d,])',
|
||||
$exporter->shortenedExport($node, 200),
|
||||
);
|
||||
}
|
||||
|
||||
public function testShortenedExportUsesDefaultRepresentationWhenNoCustomObjectExporterHandlesObject(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'stdClass Object ()',
|
||||
$exporter->shortenedExport(new stdClass),
|
||||
);
|
||||
}
|
||||
|
||||
public function testKnowsThatCustomObjectExporterProvidesRepresentationForObjectItHandles(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesObjectsOfSpecificType(ExampleClass::class),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertTrue($exporter->hasCustomRepresentationFor(new ExampleClass('bar')));
|
||||
}
|
||||
|
||||
public function testKnowsThatCustomObjectExporterDoesNotProvideRepresentationForObjectItDoesNotHandle(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesObjectsOfSpecificType(ExampleClass::class),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertFalse($exporter->hasCustomRepresentationFor(new stdClass));
|
||||
}
|
||||
|
||||
public function testKnowsThatNoCustomObjectExporterProvidesRepresentationForObjectWhenNoneIsConfigured(): void
|
||||
{
|
||||
$this->assertFalse((new Exporter)->hasCustomRepresentationFor(new stdClass));
|
||||
}
|
||||
|
||||
public function testCustomObjectExporterCanExportValuesNestedInObjectItHandles(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatExportsNestedValues,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
<<<'EOT'
|
||||
Node(Array &0 [
|
||||
'key' => 'value',
|
||||
])
|
||||
EOT,
|
||||
$exporter->export(new Node(['key' => 'value'])),
|
||||
);
|
||||
}
|
||||
|
||||
public function testArrayExportedByCustomObjectExporterDoesNotReuseReferenceToOtherArray(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatExportsNestedValues,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
<<<'EOT'
|
||||
Array &0 [
|
||||
0 => Node(Array &1 [
|
||||
'key' => 'value',
|
||||
]),
|
||||
]
|
||||
EOT,
|
||||
$exporter->export([new Node(['key' => 'value'])]),
|
||||
);
|
||||
}
|
||||
|
||||
public function testObjectNestedInItselfIsNotExportedByCustomObjectExporterAgain(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatExportsNestedValues,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$node = new Node;
|
||||
$node->children = [$node];
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
<<<'EOT'
|
||||
Node(Array &0 [
|
||||
0 => SebastianBergmann\Exporter\Node Object #%d,
|
||||
])
|
||||
EOT,
|
||||
$exporter->export($node),
|
||||
);
|
||||
}
|
||||
|
||||
public function testObjectsNestedInEachOtherAreNotExportedByCustomObjectExporterAgain(): void
|
||||
{
|
||||
$exporter = new Exporter(
|
||||
0,
|
||||
40,
|
||||
new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatExportsNestedValues,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
$first = new Node;
|
||||
$second = new Node;
|
||||
$first->children = [$second];
|
||||
$second->children = [$first];
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
<<<'EOT'
|
||||
Node(Array &0 [
|
||||
0 => Node(Array &1 [
|
||||
0 => SebastianBergmann\Exporter\Node Object #%d,
|
||||
]),
|
||||
])
|
||||
EOT,
|
||||
$exporter->export($first),
|
||||
);
|
||||
}
|
||||
|
||||
private function trimNewline(string $string): string
|
||||
{
|
||||
return (string) preg_replace('/[ ]*\n/', "\n", $string);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Small;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stdClass;
|
||||
|
||||
#[CoversClass(ObjectExporterChain::class)]
|
||||
#[UsesClass(Exporter::class)]
|
||||
#[UsesClass(ExportContext::class)]
|
||||
#[Small]
|
||||
final class ObjectExporterChainTest extends TestCase
|
||||
{
|
||||
public function testHandlesObjectWhenAtLeastOneComposedObjectExporterHandlesIt(): void
|
||||
{
|
||||
$chain = new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
],
|
||||
);
|
||||
|
||||
$this->assertTrue($chain->handles(new stdClass));
|
||||
}
|
||||
|
||||
public function testDoesNotHandleObjectWhenNoComposedObjectExporterHandlesIt(): void
|
||||
{
|
||||
$chain = new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
],
|
||||
);
|
||||
|
||||
$this->assertFalse($chain->handles(new stdClass));
|
||||
}
|
||||
|
||||
public function testDelegatesExportingToFirstComposedObjectExporterThatHandlesObject(): void
|
||||
{
|
||||
$chain = new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
new ObjectExporterThatHandlesObjectsOfSpecificType(stdClass::class),
|
||||
new ObjectExporterThatHandlesEveryObject,
|
||||
],
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
'stdClass handled by custom exporter',
|
||||
$chain->export(new stdClass, new Exporter, 0, new ExportContext),
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
ExampleClass::class . ' (indentation: 0)',
|
||||
$chain->export(new ExampleClass('foo'), new Exporter, 0, new ExportContext),
|
||||
);
|
||||
}
|
||||
|
||||
public function testCannotExportObjectWhenNoComposedObjectExporterHandlesIt(): void
|
||||
{
|
||||
$chain = new ObjectExporterChain(
|
||||
[
|
||||
new ObjectExporterThatHandlesNoObject,
|
||||
],
|
||||
);
|
||||
|
||||
$this->expectException(ObjectNotSupportedException::class);
|
||||
|
||||
$chain->export(new stdClass, new Exporter, 0, new ExportContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
final class ChildClassWithPrivateProperty extends ParentClassWithPrivateProperty
|
||||
{
|
||||
/**
|
||||
* @phpstan-ignore property.onlyWritten
|
||||
*/
|
||||
private string $property;
|
||||
|
||||
public function __construct(string $parentProperty, string $childProperty)
|
||||
{
|
||||
parent::__construct($parentProperty);
|
||||
|
||||
$this->property = $childProperty;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ namespace SebastianBergmann\Exporter;
|
|||
/*
|
||||
* Helper to test export of enums.
|
||||
*/
|
||||
class ExampleClass
|
||||
final class ExampleClass
|
||||
{
|
||||
/**
|
||||
* @phpstan-ignore property.onlyWritten
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
/*
|
||||
* Helper to test export of objects that are nested in objects.
|
||||
*/
|
||||
final class Node
|
||||
{
|
||||
/**
|
||||
* @var array<mixed>
|
||||
*/
|
||||
public array $children;
|
||||
|
||||
/**
|
||||
* @param array<mixed> $children
|
||||
*/
|
||||
public function __construct(array $children = [])
|
||||
{
|
||||
$this->children = $children;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use function assert;
|
||||
use function sprintf;
|
||||
|
||||
final readonly class ObjectExporterThatExportsNestedValues implements ObjectExporter
|
||||
{
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return $object instanceof Node;
|
||||
}
|
||||
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
assert($object instanceof Node);
|
||||
|
||||
return sprintf(
|
||||
'Node(%s)',
|
||||
$exporter->export($object->children, $indentation, $context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
final readonly class ObjectExporterThatHandlesEveryObject implements ObjectExporter
|
||||
{
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
return sprintf(
|
||||
'%s (indentation: %d)',
|
||||
$object::class,
|
||||
$indentation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
final readonly class ObjectExporterThatHandlesNoObject implements ObjectExporter
|
||||
{
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
throw new ObjectNotSupportedException;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
final readonly class ObjectExporterThatHandlesObjectsOfSpecificType implements ObjectExporter
|
||||
{
|
||||
/**
|
||||
* @var class-string
|
||||
*/
|
||||
private string $type;
|
||||
|
||||
/**
|
||||
* @param class-string $type
|
||||
*/
|
||||
public function __construct(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return $object instanceof $this->type;
|
||||
}
|
||||
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
return sprintf(
|
||||
'%s handled by custom exporter',
|
||||
$object::class,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
final readonly class ObjectExporterThatReturnsGivenRepresentation implements ObjectExporter
|
||||
{
|
||||
private string $representation;
|
||||
|
||||
public function __construct(string $representation)
|
||||
{
|
||||
$this->representation = $representation;
|
||||
}
|
||||
|
||||
public function handles(object $object): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function export(object $object, Exporter $exporter, int $indentation, ExportContext $context): string
|
||||
{
|
||||
return $this->representation;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of sebastian/exporter.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
namespace SebastianBergmann\Exporter;
|
||||
|
||||
abstract class ParentClassWithPrivateProperty
|
||||
{
|
||||
/**
|
||||
* @phpstan-ignore property.onlyWritten
|
||||
*/
|
||||
private string $property;
|
||||
|
||||
public function __construct(string $property)
|
||||
{
|
||||
$this->property = $property;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.2.7",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.12",
|
||||
"tomasvotruba/type-coverage": "^2.3.0",
|
||||
"ergebnis/phpstan-rules": "^2.13.1"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
{
|
||||
"_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": "3579343022b2a28c7195fe30b1752bfb",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "ergebnis/phpstan-rules",
|
||||
"version": "2.13.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ergebnis/phpstan-rules.git",
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"phpstan/phpstan": "^2.1.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.0.0 || ^5.0.0",
|
||||
"doctrine/orm": "^2.20.0 || ^3.3.0",
|
||||
"ergebnis/composer-normalize": "^2.49.0",
|
||||
"ergebnis/license": "^2.7.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.59.0",
|
||||
"ergebnis/phpunit-slow-test-detector": "^2.20.0",
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"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": "2026-01-27T17:13:06+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.2.7",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"reference": "692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"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"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ondřej Mirtes"
|
||||
},
|
||||
{
|
||||
"name": "Markus Staab"
|
||||
},
|
||||
{
|
||||
"name": "Vincent Langlet"
|
||||
}
|
||||
],
|
||||
"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": "2026-07-29T17:39:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-strict-rules",
|
||||
"version": "2.0.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "2bc5ae19ae965663b62ac907ee6342c3903ec93b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2bc5ae19ae965663b62ac907ee6342c3903ec93b",
|
||||
"reference": "2bc5ae19ae965663b62ac907ee6342c3903ec93b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpstan/phpstan": "^2.1.52"
|
||||
},
|
||||
"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",
|
||||
"shipmonk/name-collision-detector": "^2.1"
|
||||
},
|
||||
"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",
|
||||
"keywords": [
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.12"
|
||||
},
|
||||
"time": "2026-07-19T07:24:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tomasvotruba/type-coverage",
|
||||
"version": "2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"phpstan/phpstan": "^2.2",
|
||||
"webmozart/assert": "^1.11 || ^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpunit/phpunit": "^13.2",
|
||||
"rector/jack": "^1.0",
|
||||
"rector/rector": "^2.5",
|
||||
"shipmonk/composer-dependency-analyser": "^1.8",
|
||||
"symfony/dom-crawler": "^8.1",
|
||||
"symplify/easy-coding-standard": "^13.2",
|
||||
"tomasvotruba/unused-public": "^2.2",
|
||||
"tracy/tracy": "^2.12"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"config/extension.neon",
|
||||
"packages/type-perfect/config/extension.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Rector\\TypePerfect\\": "packages/type-perfect/src",
|
||||
"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.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.me/rectorphp",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/tomasvotruba",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-29T20:32:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "2.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webmozarts/assert.git",
|
||||
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"ext-date": "*",
|
||||
"ext-filter": "*",
|
||||
"php": "^8.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "",
|
||||
"ext-simplexml": "",
|
||||
"ext-spl": ""
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"psalm": {
|
||||
"pluginClass": "Webmozart\\Assert\\PsalmPlugin"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev",
|
||||
"dev-feature/2-0": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webmozart\\Assert\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bernhard Schussek",
|
||||
"email": "bschussek@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Woody Gilk",
|
||||
"email": "woody.gilk@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Assertions to validate method input/output with nice error messages.",
|
||||
"keywords": [
|
||||
"assert",
|
||||
"check",
|
||||
"validate"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webmozarts/assert/issues",
|
||||
"source": "https://github.com/webmozarts/assert/tree/2.4.1"
|
||||
},
|
||||
"time": "2026-06-15T15:31:57+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitf9e7218f71d5874b5632927df4f72bd7::getLoader();
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpstan/phpstan/phpstan)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->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';
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpstan/phpstan/phpstan.phar)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->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';
|
||||
|
|
@ -0,0 +1,579 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* 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 <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @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<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
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<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $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>|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>|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>|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>|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<string, self>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,396 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* 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 string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
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<string>
|
||||
*/
|
||||
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<string>
|
||||
*/
|
||||
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
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<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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 string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $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\\ShouldNotHappenException' => $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\\Process' => $vendorDir . '/nette/utils/src/Utils/Process.php',
|
||||
'Nette\\Utils\\ProcessFailedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ProcessTimeoutException' => $vendorDir . '/nette/utils/src/Utils/exceptions.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',
|
||||
);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'9b38cf48e83f5d8f60375221cd213eee' => $vendorDir . '/phpstan/phpstan/bootstrap.php',
|
||||
);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'TomasVotruba\\TypeCoverage\\' => array($vendorDir . '/tomasvotruba/type-coverage/src'),
|
||||
'PHPStan\\ExtensionInstaller\\' => array($vendorDir . '/phpstan/extension-installer/src'),
|
||||
'PHPStan\\' => array($vendorDir . '/phpstan/phpstan-strict-rules/src'),
|
||||
'Nette\\' => array($vendorDir . '/nette/utils/src'),
|
||||
'Ergebnis\\PHPStan\\Rules\\' => array($vendorDir . '/ergebnis/phpstan-rules/src'),
|
||||
);
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitf9e7218f71d5874b5632927df4f72bd7
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitf9e7218f71d5874b5632927df4f72bd7', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitf9e7218f71d5874b5632927df4f72bd7', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7::getInitializer($loader));
|
||||
|
||||
$loader->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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitf9e7218f71d5874b5632927df4f72bd7
|
||||
{
|
||||
public static $files = array (
|
||||
'9b38cf48e83f5d8f60375221cd213eee' => __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'T' =>
|
||||
array (
|
||||
'TomasVotruba\\TypeCoverage\\' => 26,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'PHPStan\\ExtensionInstaller\\' => 27,
|
||||
'PHPStan\\' => 8,
|
||||
),
|
||||
'N' =>
|
||||
array (
|
||||
'Nette\\' => 6,
|
||||
),
|
||||
'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',
|
||||
),
|
||||
'Nette\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/nette/utils/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\\ShouldNotHappenException' => __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\\Process' => __DIR__ . '/..' . '/nette/utils/src/Utils/Process.php',
|
||||
'Nette\\Utils\\ProcessFailedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ProcessTimeoutException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,404 @@
|
|||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "ergebnis/phpstan-rules",
|
||||
"version": "2.13.1",
|
||||
"version_normalized": "2.13.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ergebnis/phpstan-rules.git",
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ergebnis/phpstan-rules/zipball/f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"reference": "f69db86b98595c34fc1f61c89fe3b380141aa519",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"phpstan/phpstan": "^2.1.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.0.0 || ^5.0.0",
|
||||
"doctrine/orm": "^2.20.0 || ^3.3.0",
|
||||
"ergebnis/composer-normalize": "^2.49.0",
|
||||
"ergebnis/license": "^2.7.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.59.0",
|
||||
"ergebnis/phpunit-slow-test-detector": "^2.20.0",
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"phpunit/phpunit": "^9.6.21",
|
||||
"psr/container": "^2.0.2",
|
||||
"symfony/finder": "^5.4.45",
|
||||
"symfony/process": "^5.4.47"
|
||||
},
|
||||
"time": "2026-01-27T17:13:06+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": "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.2.7",
|
||||
"version_normalized": "2.2.7.0",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"reference": "692db47b9dddb0487934e5236e77d48594aef921",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
},
|
||||
"time": "2026-07-29T17:39:32+00:00",
|
||||
"bin": [
|
||||
"phpstan",
|
||||
"phpstan.phar"
|
||||
],
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ondřej Mirtes"
|
||||
},
|
||||
{
|
||||
"name": "Markus Staab"
|
||||
},
|
||||
{
|
||||
"name": "Vincent Langlet"
|
||||
}
|
||||
],
|
||||
"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.12",
|
||||
"version_normalized": "2.0.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
|
||||
"reference": "2bc5ae19ae965663b62ac907ee6342c3903ec93b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/2bc5ae19ae965663b62ac907ee6342c3903ec93b",
|
||||
"reference": "2bc5ae19ae965663b62ac907ee6342c3903ec93b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpstan/phpstan": "^2.1.52"
|
||||
},
|
||||
"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",
|
||||
"shipmonk/name-collision-detector": "^2.1"
|
||||
},
|
||||
"time": "2026-07-19T07:24:06+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",
|
||||
"keywords": [
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.12"
|
||||
},
|
||||
"install-path": "../phpstan/phpstan-strict-rules"
|
||||
},
|
||||
{
|
||||
"name": "tomasvotruba/type-coverage",
|
||||
"version": "2.3.0",
|
||||
"version_normalized": "2.3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/TomasVotruba/type-coverage.git",
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"reference": "7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"phpstan/phpstan": "^2.2",
|
||||
"webmozart/assert": "^1.11 || ^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpunit/phpunit": "^13.2",
|
||||
"rector/jack": "^1.0",
|
||||
"rector/rector": "^2.5",
|
||||
"shipmonk/composer-dependency-analyser": "^1.8",
|
||||
"symfony/dom-crawler": "^8.1",
|
||||
"symplify/easy-coding-standard": "^13.2",
|
||||
"tomasvotruba/unused-public": "^2.2",
|
||||
"tracy/tracy": "^2.12"
|
||||
},
|
||||
"time": "2026-07-29T20:32:37+00:00",
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"config/extension.neon",
|
||||
"packages/type-perfect/config/extension.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Rector\\TypePerfect\\": "packages/type-perfect/src",
|
||||
"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.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.paypal.me/rectorphp",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/tomasvotruba",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"install-path": "../tomasvotruba/type-coverage"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "2.4.1",
|
||||
"version_normalized": "2.4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webmozarts/assert.git",
|
||||
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||
"reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"ext-date": "*",
|
||||
"ext-filter": "*",
|
||||
"php": "^8.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "",
|
||||
"ext-simplexml": "",
|
||||
"ext-spl": ""
|
||||
},
|
||||
"time": "2026-06-15T15:31:57+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"psalm": {
|
||||
"pluginClass": "Webmozart\\Assert\\PsalmPlugin"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev",
|
||||
"dev-feature/2-0": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webmozart\\Assert\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bernhard Schussek",
|
||||
"email": "bschussek@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Woody Gilk",
|
||||
"email": "woody.gilk@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Assertions to validate method input/output with nice error messages.",
|
||||
"keywords": [
|
||||
"assert",
|
||||
"check",
|
||||
"validate"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webmozarts/assert/issues",
|
||||
"source": "https://github.com/webmozarts/assert/tree/2.4.1"
|
||||
},
|
||||
"install-path": "../webmozart/assert"
|
||||
}
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": [
|
||||
"ergebnis/phpstan-rules",
|
||||
"phpstan/extension-installer",
|
||||
"phpstan/phpstan",
|
||||
"phpstan/phpstan-strict-rules",
|
||||
"tomasvotruba/type-coverage",
|
||||
"webmozart/assert"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '4f08958e05d5bfc330b2f0b05252a7b7ddbe0558',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'__root__' => array(
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '4f08958e05d5bfc330b2f0b05252a7b7ddbe0558',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'ergebnis/phpstan-rules' => array(
|
||||
'pretty_version' => '2.13.1',
|
||||
'version' => '2.13.1.0',
|
||||
'reference' => 'f69db86b98595c34fc1f61c89fe3b380141aa519',
|
||||
'type' => 'phpstan-extension',
|
||||
'install_path' => __DIR__ . '/../ergebnis/phpstan-rules',
|
||||
'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.2.7',
|
||||
'version' => '2.2.7.0',
|
||||
'reference' => '692db47b9dddb0487934e5236e77d48594aef921',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpstan/phpstan',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'phpstan/phpstan-strict-rules' => array(
|
||||
'pretty_version' => '2.0.12',
|
||||
'version' => '2.0.12.0',
|
||||
'reference' => '2bc5ae19ae965663b62ac907ee6342c3903ec93b',
|
||||
'type' => 'phpstan-extension',
|
||||
'install_path' => __DIR__ . '/../phpstan/phpstan-strict-rules',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'tomasvotruba/type-coverage' => array(
|
||||
'pretty_version' => '2.3.0',
|
||||
'version' => '2.3.0.0',
|
||||
'reference' => '7ba48c1bf3bbb7ffe99caacc7017bae5ee72d644',
|
||||
'type' => 'phpstan-extension',
|
||||
'install_path' => __DIR__ . '/../tomasvotruba/type-coverage',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
'webmozart/assert' => array(
|
||||
'pretty_version' => '2.4.1',
|
||||
'version' => '2.4.1.0',
|
||||
'reference' => '2ccb7c2e821038c03a3e6e1700c570c158c55f70',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../webmozart/assert',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -0,0 +1,775 @@
|
|||
# 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.13.1...main`][2.13.1...main].
|
||||
|
||||
## [`2.13.1`][2.13.1]
|
||||
|
||||
For a full diff see [`2.13.0...2.13.1`][2.13.0...2.13.1].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Adjusted `Files\NoPhpstanIgnoreRule` to traverse into all nodes ([#1011]), by [@localheinz]
|
||||
|
||||
## [`2.13.0`][2.13.0]
|
||||
|
||||
For a full diff see [`2.12.0...2.13.0`][2.12.0...2.13.0].
|
||||
|
||||
### Added
|
||||
|
||||
- Added `Files\NoPhpstanIgnoreRule`, which reports an error when a `@phpstan-ignore`, `@phpstan-ignore-line`, or `@phpstan-ignore-next-line` tag is used to suppress an error ([#1009]), by [@localheinz]
|
||||
|
||||
### Changed
|
||||
|
||||
- Required `phpstan/phpstan:^2.1.35` ([#1010]), by [@localheinz]
|
||||
|
||||
## [`2.12.0`][2.12.0]
|
||||
|
||||
For a full diff see [`2.11.0...2.12.0`][2.11.0...2.12.0].
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for PHP 8.5 ([#977]), by [@localheinz]
|
||||
|
||||
## [`2.11.0`][2.11.0]
|
||||
|
||||
For a full diff see [`2.10.5...2.11.0`][2.10.5...2.11.0].
|
||||
|
||||
### Changed
|
||||
|
||||
- Allowed installation on PHP 8.5 ([#972]), by [@localheinz]
|
||||
|
||||
## [`2.10.5`][2.10.5]
|
||||
|
||||
For a full diff see [`2.10.4...2.10.5`][2.10.4...2.10.5].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Adjusted `Methods\NoNamedArgumentRule` to handle calls to constructors of variable class names ([#957]), by [@localheinz]
|
||||
- Adjusted `Methods\NoNamedArgumentRule` to describe known calls only ([#958]), by [@localheinz]
|
||||
|
||||
## [`2.10.4`][2.10.4]
|
||||
|
||||
For a full diff see [`2.10.3...2.10.4`][2.10.3...2.10.4].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Adjusted `Methods\NoNamedArgumentRule` to handle static calls on variable expressions ([#947]), by [@localheinz]
|
||||
- Adjusted `Methods\NoNamedArgumentRule` to handle calls on invokables ([#948]), by [@localheinz]
|
||||
- Adjusted `Methods\NoNamedArgumentRule` to handle calls on callables assigned to properties ([#949]), by [@localheinz]
|
||||
- Adjusted `Methods\NoNamedArgumentRule` to handle all other calls with generic error message ([#951]), by [@localheinz]
|
||||
|
||||
## [`2.10.3`][2.10.3]
|
||||
|
||||
For a full diff see [`2.10.2...2.10.3`][2.10.2...2.10.3].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Adjusted `Methods\InvokeParentHookMethodRule` to ignore comments ([#944]), by [@localheinz]
|
||||
|
||||
## [`2.10.2`][2.10.2]
|
||||
|
||||
For a full diff see [`2.10.1...2.10.2`][2.10.1...2.10.2].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Renamed error identifier for `Methods\InvokeParentHookMethodRule` ([#943]), by [@localheinz]
|
||||
|
||||
## [`2.10.1`][2.10.1]
|
||||
|
||||
For a full diff see [`2.10.0...2.10.1`][2.10.0...2.10.1].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed schema for configuration of `Methods\InvokeParentHookMethodRule` ([#940]), by [@localheinz]
|
||||
|
||||
## [`2.10.0`][2.10.0]
|
||||
|
||||
For a full diff see [`2.9.0...2.10.0`][2.9.0...2.10.0].
|
||||
|
||||
### Added
|
||||
|
||||
- Added `Methods\InvokeParentHookMethodRule`, which reports an error when a hook method that overrides a hook method in a parent class does not invoke the overridden hook method in the expected order ([#939]), by [@localheinz]
|
||||
|
||||
## [`2.9.0`][2.9.0]
|
||||
|
||||
For a full diff see [`2.8.0...2.9.0`][2.8.0...2.9.0].
|
||||
|
||||
### Added
|
||||
|
||||
- Added `CallLikes\NoNamedArgumentRule`, which reports an error when an anonymous function, a function, or a method is invoked using a named argument ([#914]), by [@localheinz]
|
||||
|
||||
### Changed
|
||||
|
||||
- Required `phpstan/phpstan:^2.1.8` ([#938]), by [@localheinz]
|
||||
|
||||
## [`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
|
||||
[2.9.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.9.0
|
||||
[2.10.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.0
|
||||
[2.10.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.1
|
||||
[2.10.2]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.2
|
||||
[2.10.3]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.3
|
||||
[2.10.4]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.4
|
||||
[2.10.5]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.10.5
|
||||
[2.11.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.11.0
|
||||
[2.12.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.12.0
|
||||
[2.13.0]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.13.0
|
||||
[2.13.1]: https://github.com/ergebnis/phpstan-rules/releases/tag/2.13.1
|
||||
|
||||
[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...2.9.0]: https://github.com/ergebnis/phpstan-rules/compare/2.8.0...2.9.0
|
||||
[2.9.0...2.10.0]: https://github.com/ergebnis/phpstan-rules/compare/2.9.0...2.10.0
|
||||
[2.10.0...2.10.1]: https://github.com/ergebnis/phpstan-rules/compare/2.10.0...2.10.1
|
||||
[2.10.1...2.10.2]: https://github.com/ergebnis/phpstan-rules/compare/2.10.1...2.10.2
|
||||
[2.10.2...2.10.3]: https://github.com/ergebnis/phpstan-rules/compare/2.10.2...2.10.3
|
||||
[2.10.3...2.10.4]: https://github.com/ergebnis/phpstan-rules/compare/2.10.3...2.10.4
|
||||
[2.10.4...2.10.5]: https://github.com/ergebnis/phpstan-rules/compare/2.10.4...2.10.5
|
||||
[2.10.5...2.11.0]: https://github.com/ergebnis/phpstan-rules/compare/2.10.5...2.11.0
|
||||
[2.11.0...2.12.0]: https://github.com/ergebnis/phpstan-rules/compare/2.11.0...2.12.0
|
||||
[2.12.0...2.13.0]: https://github.com/ergebnis/phpstan-rules/compare/2.12.0...2.13.0
|
||||
[2.13.0...2.13.1]: https://github.com/ergebnis/phpstan-rules/compare/2.13.0...2.13.1
|
||||
[2.13.1...main]: https://github.com/ergebnis/phpstan-rules/compare/2.13.1...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
|
||||
[#938]: https://github.com/ergebnis/phpstan-rules/pull/938
|
||||
[#939]: https://github.com/ergebnis/phpstan-rules/pull/939
|
||||
[#940]: https://github.com/ergebnis/phpstan-rules/pull/940
|
||||
[#943]: https://github.com/ergebnis/phpstan-rules/pull/943
|
||||
[#944]: https://github.com/ergebnis/phpstan-rules/pull/944
|
||||
[#947]: https://github.com/ergebnis/phpstan-rules/pull/947
|
||||
[#948]: https://github.com/ergebnis/phpstan-rules/pull/948
|
||||
[#949]: https://github.com/ergebnis/phpstan-rules/pull/949
|
||||
[#951]: https://github.com/ergebnis/phpstan-rules/pull/951
|
||||
[#957]: https://github.com/ergebnis/phpstan-rules/pull/957
|
||||
[#958]: https://github.com/ergebnis/phpstan-rules/pull/958
|
||||
[#972]: https://github.com/ergebnis/phpstan-rules/pull/972
|
||||
[#977]: https://github.com/ergebnis/phpstan-rules/pull/977
|
||||
[#1009]: https://github.com/ergebnis/phpstan-rules/pull/1009
|
||||
[#1010]: https://github.com/ergebnis/phpstan-rules/pull/1010
|
||||
[#1011]: https://github.com/ergebnis/phpstan-rules/pull/1011
|
||||
|
||||
[@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
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2026 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.
|
||||
|
|
@ -0,0 +1,776 @@
|
|||
# phpstan-rules
|
||||
|
||||
[](https://github.com/ergebnis/phpstan-rules/actions)
|
||||
[](https://github.com/ergebnis/phpstan-rules/actions)
|
||||
[](https://github.com/ergebnis/phpstan-rules/actions)
|
||||
[](https://github.com/ergebnis/phpstan-rules/actions)
|
||||
|
||||
[](https://codecov.io/gh/ergebnis/phpstan-rules)
|
||||
|
||||
[](https://packagist.org/packages/ergebnis/phpstan-rules)
|
||||
[](https://packagist.org/packages/ergebnis/phpstan-rules)
|
||||
[](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\CallLikes\NoNamedArgumentRule`](https://github.com/ergebnis/phpstan-rules#calllikesnonamedargumentrule)
|
||||
- [`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\Files\NoPhpstanIgnoreRule`](https://github.com/ergebnis/phpstan-rules#filesnophpstanignorerule)
|
||||
- [`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\InvokeParentHookMethodRule`](https://github.com/ergebnis/phpstan-rules#methodsinvokeparenthookmethodrule)
|
||||
- [`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)
|
||||
|
||||
|
||||
### CallLikes
|
||||
|
||||
#### `CallLikes\NoNamedArgumentRule`
|
||||
|
||||
This rule reports an error when an anonymous function, a function, or a method is invoked using a [named argument](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments).
|
||||
|
||||
##### Disabling the rule
|
||||
|
||||
You can set the `enabled` parameter to `false` to disable this rule.
|
||||
|
||||
```neon
|
||||
parameters:
|
||||
ergebnis:
|
||||
noNamedArgument:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### 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/6.0.0/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\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\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\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
|
||||
```
|
||||
|
||||
#### `Files\NoPhpstanIgnoreRule`
|
||||
|
||||
This rule reports an error when a `@phpstan-ignore`, `@phpstan-ignore-line`, or `@phpstan-ignore-next-line` tag is used to [ignore errors reported by `phpstan/phpstan`](https://phpstan.org/user-guide/ignoring-errors#ignoring-in-code-using-phpdocs).
|
||||
|
||||
##### Disabling the rule
|
||||
|
||||
You can set the `enabled` parameter to `false` to disable this rule.
|
||||
|
||||
```neon
|
||||
parameters:
|
||||
ergebnis:
|
||||
noPhpstanIgnore:
|
||||
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\InvokeParentHookMethodRule`
|
||||
|
||||
This rule reports an error when a hook method that overrides a hook method in a parent class does not invoke the overridden hook method in the expected order.
|
||||
|
||||
##### Defaults
|
||||
|
||||
By default, this rule requires the following hook methods to be invoked before doing something in the overriding method:
|
||||
|
||||
- [`Codeception\PHPUnit\TestCase::_setUp()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L11-L13)
|
||||
- [`Codeception\PHPUnit\TestCase::_setUpBeforeClass()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L25-L27)
|
||||
- [`Codeception\Test\Unit::_before()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L63-L65)
|
||||
- [`Codeception\Test\Unit::_setUp()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L34-L58)
|
||||
- [`PHPUnit\Framework\TestCase::assertPreConditions()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2073-L2075)
|
||||
- [`PHPUnit\Framework\TestCase::setUp()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2063-L2065)
|
||||
- [`PHPUnit\Framework\TestCase::setUpBeforeClass()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2055-L2057)
|
||||
|
||||
By default, this rule requires the following hook methods to be invoked after doing something in the overriding method:
|
||||
|
||||
- [`Codeception\PHPUnit\TestCase::_tearDown()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L18-L20)
|
||||
- [`Codeception\PHPUnit\TestCase::_tearDownAfterClass()`](https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L32-L34)
|
||||
- [`Codeception\Test\Unit::_after()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L75-L77)
|
||||
- [`Codeception\Test\Unit::_tearDown()`](https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L67-L70)
|
||||
- [`PHPUnit\Framework\TestCase::assertPostConditions()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2083-L2085)
|
||||
- [`PHPUnit\Framework\TestCase::tearDown()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2091-L2093)
|
||||
- [`PHPUnit\Framework\TestCase::tearDownAfterClass()`](https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2098-L2100)
|
||||
|
||||
##### Disabling the rule
|
||||
|
||||
You can set the `enabled` parameter to `false` to disable this rule.
|
||||
|
||||
```neon
|
||||
parameters:
|
||||
ergebnis:
|
||||
invokeParentHookMethod:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
##### Configuring methods to invoke the parent method in the right order:
|
||||
|
||||
You can set the `hookMethods` parameter to a list of hook methods:
|
||||
|
||||
```neon
|
||||
parameters:
|
||||
ergebnis:
|
||||
invokeParentHookMethod:
|
||||
hookMethods:
|
||||
- className: "Example\Test\Functional\AbstractCest"
|
||||
methodName: "_before"
|
||||
hasContent: "yes"
|
||||
invocation: "first"
|
||||
```
|
||||
|
||||
- `className`: name of the class that declares the hook method
|
||||
- `methodName`: name of the hook method
|
||||
- `hasContent`: one of `"yes"`, `"no"`, `"maybe"`
|
||||
- `invocation`: one of `"any"` (needs to be invoked), `"first"` (needs to be invoked before all other statements in the overriding hook method, `"last"` (needs to be invoked after all other statements in the overriding hook method)
|
||||
|
||||
#### `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 @ergebnis](https://github.com/sponsors/ergebnis).
|
||||
|
||||
## 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.
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"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 || ~8.5.0",
|
||||
"ext-mbstring": "*",
|
||||
"phpstan/phpstan": "^2.1.35"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.0.0 || ^5.0.0",
|
||||
"doctrine/orm": "^2.20.0 || ^3.3.0",
|
||||
"ergebnis/composer-normalize": "^2.49.0",
|
||||
"ergebnis/license": "^2.7.0",
|
||||
"ergebnis/php-cs-fixer-config": "^6.59.0",
|
||||
"ergebnis/phpunit-slow-test-detector": "^2.20.0",
|
||||
"fakerphp/faker": "^1.24.1",
|
||||
"phpstan/extension-installer": "^1.4.3",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.12",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.8",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
conditionalTags:
|
||||
Ergebnis\PHPStan\Rules\CallLikes\NoNamedArgumentRule:
|
||||
phpstan.rules.rule: %ergebnis.noNamedArgument.enabled%
|
||||
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\Files\NoPhpstanIgnoreRule:
|
||||
phpstan.rules.rule: %ergebnis.noPhpstanIgnore.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\InvokeParentHookMethodRule:
|
||||
phpstan.rules.rule: %ergebnis.invokeParentHookMethod.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%
|
||||
invokeParentHookMethod:
|
||||
enabled: %ergebnis.allRules%
|
||||
hookMethods: []
|
||||
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%
|
||||
noNamedArgument:
|
||||
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%
|
||||
noPhpstanIgnore:
|
||||
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(),
|
||||
])
|
||||
invokeParentHookMethod: structure([
|
||||
enabled: bool(),
|
||||
hookMethods: listOf(structure([
|
||||
className: string(),
|
||||
hasContent: anyOf("no", "yes"),
|
||||
invocation: anyOf("any", "first", "last"),
|
||||
methodName: string(),
|
||||
]))
|
||||
])
|
||||
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(),
|
||||
])
|
||||
noNamedArgument: 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(),
|
||||
])
|
||||
noPhpstanIgnore: 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\CallLikes\NoNamedArgumentRule
|
||||
|
||||
-
|
||||
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\Files\NoPhpstanIgnoreRule
|
||||
|
||||
-
|
||||
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\InvokeParentHookMethodRule
|
||||
arguments:
|
||||
hookMethods: %ergebnis.invokeParentHookMethod.hookMethods%
|
||||
|
||||
-
|
||||
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
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Analyzer
|
||||
{
|
||||
public function hasNullDefaultValue(Node\Param $parameter): bool
|
||||
{
|
||||
if (!$parameter->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;
|
||||
}
|
||||
}
|
||||
175
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/CallLikes/NoNamedArgumentRule.php
vendored
Normal file
175
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/CallLikes/NoNamedArgumentRule.php
vendored
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\CallLikes;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
use PHPStan\ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\CallLike>
|
||||
*/
|
||||
final class NoNamedArgumentRule implements Rules\Rule
|
||||
{
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return Node\Expr\CallLike::class;
|
||||
}
|
||||
|
||||
public function processNode(
|
||||
Node $node,
|
||||
Analyser\Scope $scope
|
||||
): array {
|
||||
if (0 === \count($node->getArgs())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var list<Node\Arg> $namedArguments */
|
||||
$namedArguments = \array_values(\array_filter($node->getArgs(), static function (Node\Arg $argument): bool {
|
||||
if (!$argument->name instanceof Node\Identifier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
|
||||
if (0 === \count($namedArguments)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$callLike = self::describeCallLike(
|
||||
$node,
|
||||
$scope,
|
||||
);
|
||||
|
||||
return \array_map(static function (Node\Arg $namedArgument) use ($callLike): Rules\RuleError {
|
||||
/** @var Node\Identifier $argumentName */
|
||||
$argumentName = $namedArgument->name;
|
||||
|
||||
$message = \sprintf(
|
||||
'%s is invoked with named argument for parameter $%s.',
|
||||
$callLike,
|
||||
$argumentName->toString(),
|
||||
);
|
||||
|
||||
return Rules\RuleErrorBuilder::message($message)
|
||||
->identifier(ErrorIdentifier::noNamedArgument()->toString())
|
||||
->build();
|
||||
}, $namedArguments);
|
||||
}
|
||||
|
||||
private static function describeCallLike(
|
||||
Node\Expr\CallLike $node,
|
||||
Analyser\Scope $scope
|
||||
): string {
|
||||
if ($node instanceof Node\Expr\FuncCall) {
|
||||
$functionName = $node->name;
|
||||
|
||||
if ($functionName instanceof Node\Expr\PropertyFetch) {
|
||||
return \sprintf(
|
||||
'Callable referenced by property $%s',
|
||||
$functionName->name,
|
||||
);
|
||||
}
|
||||
|
||||
if ($functionName instanceof Node\Expr\Variable) {
|
||||
return \sprintf(
|
||||
'Callable referenced by $%s',
|
||||
$functionName->name,
|
||||
);
|
||||
}
|
||||
|
||||
if ($functionName instanceof Node\Name) {
|
||||
return \sprintf(
|
||||
'Function %s()',
|
||||
$functionName->name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Expr\MethodCall) {
|
||||
$methodName = $node->name;
|
||||
|
||||
if ($methodName instanceof Node\Identifier) {
|
||||
$objectType = $scope->getType($node->var);
|
||||
|
||||
$methodReflection = $scope->getMethodReflection(
|
||||
$objectType,
|
||||
$methodName->name,
|
||||
);
|
||||
|
||||
if (null === $methodReflection) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$declaringClass = $methodReflection->getDeclaringClass();
|
||||
|
||||
if ($declaringClass->isAnonymous()) {
|
||||
return \sprintf(
|
||||
'Method %s() of anonymous class',
|
||||
$methodName->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return \sprintf(
|
||||
'Method %s::%s()',
|
||||
$declaringClass->getName(),
|
||||
$methodName->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return 'Method';
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Expr\StaticCall) {
|
||||
$methodName = $node->name;
|
||||
|
||||
if ($methodName instanceof Node\Identifier) {
|
||||
$className = $node->class;
|
||||
|
||||
if ($className instanceof Node\Name) {
|
||||
return \sprintf(
|
||||
'Method %s::%s()',
|
||||
$className->toString(),
|
||||
$methodName->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return \sprintf(
|
||||
'Method %s()',
|
||||
$methodName->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return 'Method';
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Expr\New_) {
|
||||
$className = $node->class;
|
||||
|
||||
if ($className instanceof Node\Name) {
|
||||
return \sprintf(
|
||||
'Constructor of %s',
|
||||
$className->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return 'Constructor';
|
||||
}
|
||||
|
||||
return 'Callable';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ClassName
|
||||
{
|
||||
private string $value;
|
||||
|
||||
/**
|
||||
* @param class-string $value
|
||||
*/
|
||||
private function __construct(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $value
|
||||
*/
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function equals(self $other): bool
|
||||
{
|
||||
return $this->value === $other->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Classes;
|
||||
|
||||
use Doctrine\ORM;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Class_>
|
||||
*/
|
||||
final class FinalRule implements Rules\Rule
|
||||
{
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private static array $whitelistedAnnotations = [
|
||||
'Entity',
|
||||
'ORM\Entity',
|
||||
'ORM\Mapping\Entity',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<class-string>
|
||||
*/
|
||||
private static array $whitelistedAttributes = [
|
||||
ORM\Mapping\Entity::class,
|
||||
];
|
||||
private bool $allowAbstractClasses;
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $classesNotRequiredToBeAbstractOrFinal;
|
||||
private string $errorMessageTemplate = 'Class %s is not final.';
|
||||
|
||||
/**
|
||||
* @param list<class-string> $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 (self::hasWhitelistedAnnotation($node)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (self::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 static 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 static 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Classes;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
use PHPUnit\Framework;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Class_>
|
||||
*/
|
||||
final class NoExtendsRule implements Rules\Rule
|
||||
{
|
||||
/**
|
||||
* @var list<class-string>
|
||||
*/
|
||||
private static array $defaultClassesAllowedToBeExtended = [
|
||||
Framework\TestCase::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<class-string>
|
||||
*/
|
||||
private array $classesAllowedToBeExtended;
|
||||
|
||||
/**
|
||||
* @param list<class-string> $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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Classes\PHPUnit\Framework;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Class_>
|
||||
*/
|
||||
final class TestCaseWithSuffixRule implements Rules\Rule
|
||||
{
|
||||
/**
|
||||
* @var list<class-string>
|
||||
*/
|
||||
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->isSubclassOfClass($this->reflectionProvider->getClass($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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Closures;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\Closure>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
64
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php
vendored
Normal file
64
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Closures/NoParameterPassedByReferenceRule.php
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Closures;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\Closure>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Closures;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\Closure>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Closures;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\Closure>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ErrorIdentifier
|
||||
{
|
||||
private string $value;
|
||||
|
||||
private function __construct(string $value)
|
||||
{
|
||||
$this->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 invokeParentHookMethod(): self
|
||||
{
|
||||
return new self('invokeParentHookMethod');
|
||||
}
|
||||
|
||||
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 noNamedArgument(): self
|
||||
{
|
||||
return new self('noNamedArgument');
|
||||
}
|
||||
|
||||
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 noPhpstanIgnore(): self
|
||||
{
|
||||
return new self('noPhpstanIgnore');
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php
vendored
Normal file
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoAssignByReferenceRule.php
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Expressions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\AssignRef>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
49
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php
vendored
Normal file
49
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoCompactRule.php
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Expressions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\FuncCall>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php
vendored
Normal file
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoErrorSuppressionRule.php
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Expressions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\ErrorSuppress>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Expressions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\Eval_>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php
vendored
Normal file
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Expressions/NoIssetRule.php
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Expressions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Expr\Isset_>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
70
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php
vendored
Normal file
70
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/DeclareStrictTypesRule.php
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Files;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Node\FileNode;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<FileNode>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
70
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/NoPhpstanIgnoreRule.php
vendored
Normal file
70
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Files/NoPhpstanIgnoreRule.php
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Files;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeFinder;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Node\FileNode;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<FileNode>
|
||||
*/
|
||||
final class NoPhpstanIgnoreRule implements Rules\Rule
|
||||
{
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return FileNode::class;
|
||||
}
|
||||
|
||||
public function processNode(
|
||||
Node $node,
|
||||
Analyser\Scope $scope
|
||||
): array {
|
||||
$nodeFinder = new NodeFinder();
|
||||
|
||||
$foundNodes = $nodeFinder->find($node->getNodes(), static function (Node $node): bool {
|
||||
return true;
|
||||
});
|
||||
|
||||
$errors = [];
|
||||
|
||||
foreach ($foundNodes as $foundNode) {
|
||||
foreach ($foundNode->getComments() as $comment) {
|
||||
foreach (\explode("\n", $comment->getText()) as $index => $line) {
|
||||
if (0 === \preg_match('/@phpstan-ignore(-line|-next-line)?(?=\s|$|,)/', $line, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tag = $matches[0];
|
||||
|
||||
$message = \sprintf(
|
||||
'Errors reported by phpstan/phpstan should not be ignored via "%s", fix the error or use the baseline instead.',
|
||||
$tag,
|
||||
);
|
||||
|
||||
$errors[] = Rules\RuleErrorBuilder::message($message)
|
||||
->identifier(ErrorIdentifier::noPhpstanIgnore()->toString())
|
||||
->line($comment->getStartLine() + $index)
|
||||
->nonIgnorable()
|
||||
->build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Functions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Function_>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
67
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php
vendored
Normal file
67
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoParameterPassedByReferenceRule.php
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Functions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Function_>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Functions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Function_>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Functions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Function_>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
50
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php
vendored
Normal file
50
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Functions/NoReturnByReferenceRule.php
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Functions;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Function_>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HasContent
|
||||
{
|
||||
private string $value;
|
||||
|
||||
private function __construct(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
$values = [
|
||||
'maybe',
|
||||
'no',
|
||||
'yes',
|
||||
];
|
||||
|
||||
if (!\in_array($value, $values, true)) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'Value needs to be one of "%s", got "%s" instead.',
|
||||
\implode('", "', $values),
|
||||
$value,
|
||||
));
|
||||
}
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public static function maybe(): self
|
||||
{
|
||||
return new self('maybe');
|
||||
}
|
||||
|
||||
public static function no(): self
|
||||
{
|
||||
return new self('no');
|
||||
}
|
||||
|
||||
public static function yes(): self
|
||||
{
|
||||
return new self('yes');
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function equals(self $other): bool
|
||||
{
|
||||
return $this->value === $other->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HookMethod
|
||||
{
|
||||
private ClassName $className;
|
||||
private MethodName $methodName;
|
||||
private Invocation $invocation;
|
||||
private HasContent $hasContent;
|
||||
|
||||
private function __construct(
|
||||
ClassName $className,
|
||||
MethodName $methodName,
|
||||
Invocation $invocation,
|
||||
HasContent $hasContent
|
||||
) {
|
||||
$this->className = $className;
|
||||
$this->methodName = $methodName;
|
||||
$this->invocation = $invocation;
|
||||
$this->hasContent = $hasContent;
|
||||
}
|
||||
|
||||
public static function create(
|
||||
ClassName $className,
|
||||
MethodName $methodName,
|
||||
Invocation $invocation,
|
||||
HasContent $hasContent
|
||||
): self {
|
||||
return new self(
|
||||
$className,
|
||||
$methodName,
|
||||
$invocation,
|
||||
$hasContent,
|
||||
);
|
||||
}
|
||||
|
||||
public function className(): ClassName
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
public function methodName(): MethodName
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
public function invocation(): Invocation
|
||||
{
|
||||
return $this->invocation;
|
||||
}
|
||||
|
||||
public function hasContent(): HasContent
|
||||
{
|
||||
return $this->hasContent;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Invocation
|
||||
{
|
||||
private string $value;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $value
|
||||
*/
|
||||
private function __construct(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
$values = [
|
||||
'any',
|
||||
'first',
|
||||
'last',
|
||||
'never',
|
||||
];
|
||||
|
||||
if (!\in_array($value, $values, true)) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'Value needs to be one of "%s", got "%s" instead.',
|
||||
\implode('", "', $values),
|
||||
$value,
|
||||
));
|
||||
}
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public static function any(): self
|
||||
{
|
||||
return new self('any');
|
||||
}
|
||||
|
||||
public static function first(): self
|
||||
{
|
||||
return new self('first');
|
||||
}
|
||||
|
||||
public static function last(): self
|
||||
{
|
||||
return new self('last');
|
||||
}
|
||||
|
||||
public static function never(): self
|
||||
{
|
||||
return new self('never');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function equals(self $other): bool
|
||||
{
|
||||
return $this->value === $other->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class MethodName
|
||||
{
|
||||
private string $value;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $value
|
||||
*/
|
||||
private function __construct(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $value
|
||||
*/
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function equals(self $other): bool
|
||||
{
|
||||
return $this->value === $other->value;
|
||||
}
|
||||
}
|
||||
124
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php
vendored
Normal file
124
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/FinalInAbstractClassRule.php
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Doctrine\ORM;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\PhpDoc;
|
||||
use PHPStan\PhpDocParser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
final class FinalInAbstractClassRule implements Rules\Rule
|
||||
{
|
||||
private const DOCTRINE_ATTRIBUTE_NAMES = [
|
||||
ORM\Mapping\Embeddable::class,
|
||||
ORM\Mapping\Entity::class,
|
||||
];
|
||||
private const DOCTRINE_ANNOTATION_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_ANNOTATION_NAMES, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
436
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/InvokeParentHookMethodRule.php
vendored
Normal file
436
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/InvokeParentHookMethodRule.php
vendored
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Codeception\PHPUnit;
|
||||
use Codeception\Test;
|
||||
use Ergebnis\PHPStan\Rules\ClassName;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use Ergebnis\PHPStan\Rules\HasContent;
|
||||
use Ergebnis\PHPStan\Rules\HookMethod;
|
||||
use Ergebnis\PHPStan\Rules\Invocation;
|
||||
use Ergebnis\PHPStan\Rules\MethodName;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
use PHPStan\ShouldNotHappenException;
|
||||
use PHPUnit\Framework;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
final class InvokeParentHookMethodRule implements Rules\Rule
|
||||
{
|
||||
private Reflection\ReflectionProvider $reflectionProvider;
|
||||
|
||||
/**
|
||||
* @var list<HookMethod>
|
||||
*/
|
||||
private array $hookMethods;
|
||||
|
||||
/**
|
||||
* @param array<class-string, array<string, string>> $hookMethods
|
||||
*/
|
||||
public function __construct(
|
||||
Reflection\ReflectionProvider $reflectionProvider,
|
||||
array $hookMethods = []
|
||||
) {
|
||||
$this->reflectionProvider = $reflectionProvider;
|
||||
$this->hookMethods = self::sort(
|
||||
$reflectionProvider,
|
||||
...self::filter(
|
||||
$reflectionProvider,
|
||||
...\array_merge(
|
||||
self::defaultHookMethods(),
|
||||
\array_map(static function (array $hookMethod): HookMethod {
|
||||
return HookMethod::create(
|
||||
ClassName::fromString($hookMethod['className']),
|
||||
MethodName::fromString($hookMethod['methodName']),
|
||||
Invocation::fromString($hookMethod['invocation']),
|
||||
HasContent::fromString($hookMethod['hasContent']),
|
||||
);
|
||||
}, $hookMethods),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function getNodeType(): string
|
||||
{
|
||||
return Node\Stmt\ClassMethod::class;
|
||||
}
|
||||
|
||||
public function processNode(
|
||||
Node $node,
|
||||
Analyser\Scope $scope
|
||||
): array {
|
||||
$classReflection = $scope->getClassReflection();
|
||||
|
||||
if (null === $classReflection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parentClassReflection = $classReflection->getParentClass();
|
||||
|
||||
if (null === $parentClassReflection) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$methodName = $node->name->toString();
|
||||
|
||||
$hookMethod = $this->findMatchingHookMethod(
|
||||
$scope,
|
||||
$parentClassReflection,
|
||||
$classReflection,
|
||||
$methodName,
|
||||
);
|
||||
|
||||
if (!$hookMethod instanceof HookMethod) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$statements = $node->getStmts();
|
||||
|
||||
if (!\is_array($statements)) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$parentHookMethodInvocation = self::findParentHookMethodInvocation(
|
||||
\array_values($statements),
|
||||
$hookMethod,
|
||||
);
|
||||
|
||||
if ($parentHookMethodInvocation->equals(Invocation::never())) {
|
||||
if ($hookMethod->hasContent()->equals(HasContent::no())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$message = \sprintf(
|
||||
'Method %s::%s() does not invoke parent::%s().',
|
||||
$classReflection->getName(),
|
||||
$methodName,
|
||||
$hookMethod->methodName()->toString(),
|
||||
);
|
||||
|
||||
return [
|
||||
Rules\RuleErrorBuilder::message($message)
|
||||
->identifier(ErrorIdentifier::invokeParentHookMethod()->toString())
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($parentHookMethodInvocation->equals($hookMethod->invocation())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($hookMethod->invocation()->equals(Invocation::first())) {
|
||||
$message = \sprintf(
|
||||
'Method %s::%s() does not invoke parent::%s() before all other statements.',
|
||||
$classReflection->getName(),
|
||||
$methodName,
|
||||
$hookMethod->methodName()->toString(),
|
||||
);
|
||||
|
||||
return [
|
||||
Rules\RuleErrorBuilder::message($message)
|
||||
->identifier(ErrorIdentifier::invokeParentHookMethod()->toString())
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
|
||||
if ($hookMethod->invocation()->equals(Invocation::last())) {
|
||||
$message = \sprintf(
|
||||
'Method %s::%s() does not invoke parent::%s() after all other statements.',
|
||||
$classReflection->getName(),
|
||||
$methodName,
|
||||
$hookMethod->methodName()->toString(),
|
||||
);
|
||||
|
||||
return [
|
||||
Rules\RuleErrorBuilder::message($message)
|
||||
->identifier(ErrorIdentifier::invokeParentHookMethod()->toString())
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
private function findMatchingHookMethod(
|
||||
Analyser\Scope $scope,
|
||||
Reflection\ClassReflection $parentClassReflection,
|
||||
Reflection\ClassReflection $classReflection,
|
||||
string $methodName
|
||||
): ?HookMethod {
|
||||
foreach ($this->hookMethods as $hookMethod) {
|
||||
if (!$classReflection->isSubclassOfClass($this->reflectionProvider->getClass($hookMethod->className()->toString()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\mb_strtolower($hookMethod->methodName()->toString()) !== \mb_strtolower($methodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentMethodReflection = $parentClassReflection->getMethod(
|
||||
$methodName,
|
||||
$scope,
|
||||
);
|
||||
|
||||
$declaringClassReflection = $parentMethodReflection->getDeclaringClass();
|
||||
|
||||
if (\mb_strtolower($hookMethod->className()->toString()) !== \mb_strtolower($declaringClassReflection->getName())) {
|
||||
return HookMethod::create(
|
||||
ClassName::fromString($declaringClassReflection->getName()),
|
||||
MethodName::fromString($methodName),
|
||||
$hookMethod->invocation(),
|
||||
HasContent::maybe(),
|
||||
);
|
||||
}
|
||||
|
||||
return $hookMethod;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Node\Stmt> $statements
|
||||
*/
|
||||
private static function findParentHookMethodInvocation(
|
||||
array $statements,
|
||||
HookMethod $hookMethod
|
||||
): Invocation {
|
||||
$statementsWithOperations = \array_filter($statements, static function (Node $statement): bool {
|
||||
if ($statement instanceof Node\Stmt\Nop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$statementCount = \count($statementsWithOperations);
|
||||
|
||||
foreach ($statementsWithOperations as $index => $statement) {
|
||||
if (!$statement instanceof Node\Stmt\Expression) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$statement->expr instanceof Node\Expr\StaticCall) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$statement->expr->class instanceof Node\Name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$className = (string) $statement->expr->class;
|
||||
|
||||
if (\mb_strtolower($className) !== 'parent') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$statement->expr->name instanceof Node\Identifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\mb_strtolower($statement->expr->name->toString()) === \mb_strtolower($hookMethod->methodName()->toString())) {
|
||||
if (1 === $statementCount) {
|
||||
return $hookMethod->invocation();
|
||||
}
|
||||
|
||||
if (0 === $index) {
|
||||
return Invocation::first();
|
||||
}
|
||||
|
||||
if ($statementCount - 1 === $index) {
|
||||
return Invocation::last();
|
||||
}
|
||||
|
||||
return Invocation::any();
|
||||
}
|
||||
}
|
||||
|
||||
return Invocation::never();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<HookMethod>
|
||||
*/
|
||||
private static function filter(
|
||||
Reflection\ReflectionProvider $reflectionProvider,
|
||||
HookMethod ...$hookMethods
|
||||
): array {
|
||||
return \array_values(\array_filter($hookMethods, static function (HookMethod $hookMethod) use ($reflectionProvider): bool {
|
||||
return $reflectionProvider->hasClass($hookMethod->className()->toString());
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<HookMethod>
|
||||
*/
|
||||
private static function sort(
|
||||
Reflection\ReflectionProvider $reflectionProvider,
|
||||
HookMethod ...$hookMethods
|
||||
): array {
|
||||
\usort($hookMethods, static function (HookMethod $a, HookMethod $b) use ($reflectionProvider): int {
|
||||
if (\mb_strtolower($a->className()->toString()) === \mb_strtolower($b->className()->toString())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($reflectionProvider->getClass($a->className()->toString())->isSubclassOfClass($reflectionProvider->getClass($b->className()->toString()))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
|
||||
return $hookMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<HookMethod>
|
||||
*/
|
||||
private static function defaultHookMethods(): array
|
||||
{
|
||||
return [
|
||||
/**
|
||||
* @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2083-L2085
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Framework\TestCase::class),
|
||||
MethodName::fromString('assertPostConditions'),
|
||||
Invocation::last(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2073-L2075
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Framework\TestCase::class),
|
||||
MethodName::fromString('assertPreConditions'),
|
||||
Invocation::first(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2063-L2065
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Framework\TestCase::class),
|
||||
MethodName::fromString('setUp'),
|
||||
Invocation::first(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2055-L2057
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Framework\TestCase::class),
|
||||
MethodName::fromString('setUpBeforeClass'),
|
||||
Invocation::first(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2091-L2093
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Framework\TestCase::class),
|
||||
MethodName::fromString('tearDown'),
|
||||
Invocation::last(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/sebastianbergmann/phpunit/blob/6.0.0/src/Framework/TestCase.php#L2098-L2100
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Framework\TestCase::class),
|
||||
MethodName::fromString('tearDownAfterClass'),
|
||||
Invocation::last(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L11-L13
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(PHPUnit\TestCase::class),
|
||||
MethodName::fromString('_setUp'),
|
||||
Invocation::first(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L25-L27
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(PHPUnit\TestCase::class),
|
||||
MethodName::fromString('_setUpBeforeClass'),
|
||||
Invocation::first(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L18-L20
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(PHPUnit\TestCase::class),
|
||||
MethodName::fromString('_tearDown'),
|
||||
Invocation::last(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/phpunit-wrapper/blob/9.0.0/src/TestCase.php#L32-L34
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(PHPUnit\TestCase::class),
|
||||
MethodName::fromString('_tearDownAfterClass'),
|
||||
Invocation::last(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L75-L77
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Test\Unit::class),
|
||||
MethodName::fromString('_after'),
|
||||
Invocation::last(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L63-L65
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Test\Unit::class),
|
||||
MethodName::fromString('_before'),
|
||||
Invocation::first(),
|
||||
HasContent::no(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L34-L58
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Test\Unit::class),
|
||||
MethodName::fromString('_setUp'),
|
||||
Invocation::first(),
|
||||
HasContent::yes(),
|
||||
),
|
||||
/**
|
||||
* @see https://github.com/Codeception/Codeception/blob/4.2.2/src/Codeception/Test/Unit.php#L67-L70
|
||||
*/
|
||||
HookMethod::create(
|
||||
ClassName::fromString(Test\Unit::class),
|
||||
MethodName::fromString('_tearDown'),
|
||||
Invocation::last(),
|
||||
HasContent::yes(),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
76
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php
vendored
Normal file
76
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoNullableReturnTypeDeclarationRule.php
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
94
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php
vendored
Normal file
94
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterPassedByReferenceRule.php
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
final class NoParameterWithContainerTypeDeclarationRule implements Rules\Rule
|
||||
{
|
||||
private Reflection\ReflectionProvider $reflectionProvider;
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $interfacesImplementedByContainers;
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $methodsAllowedToUseContainerTypeDeclarations;
|
||||
|
||||
/**
|
||||
* @param list<string> $interfacesImplementedByContainers
|
||||
* @param list<string> $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();
|
||||
}
|
||||
}
|
||||
102
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php
vendored
Normal file
102
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoParameterWithNullDefaultValueRule.php
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\Analyzer;
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
72
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php
vendored
Normal file
72
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/NoReturnByReferenceRule.php
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
198
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php
vendored
Normal file
198
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Methods/PrivateInFinalClassRule.php
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Methods;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Reflection;
|
||||
use PHPStan\Rules;
|
||||
use PHPStan\Type;
|
||||
use PHPUnit\Framework;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\ClassMethod>
|
||||
*/
|
||||
final class PrivateInFinalClassRule implements Rules\Rule
|
||||
{
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private static array $whitelistedAnnotations = [
|
||||
'@after',
|
||||
'@before',
|
||||
'@postCondition',
|
||||
'@preCondition',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<class-string>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php
vendored
Normal file
41
tools/.phpstan/vendor/ergebnis/phpstan-rules/src/Statements/NoSwitchRule.php
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Copyright (c) 2018-2026 Andreas Möller
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE.md file that was distributed with this source code.
|
||||
*
|
||||
* @see https://github.com/ergebnis/phpstan-rules
|
||||
*/
|
||||
|
||||
namespace Ergebnis\PHPStan\Rules\Statements;
|
||||
|
||||
use Ergebnis\PHPStan\Rules\ErrorIdentifier;
|
||||
use PhpParser\Node;
|
||||
use PHPStan\Analyser;
|
||||
use PHPStan\Rules;
|
||||
|
||||
/**
|
||||
* @implements Rules\Rule<Node\Stmt\Switch_>
|
||||
*/
|
||||
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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 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.
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# PHPStan Extension Installer
|
||||
|
||||
[](https://github.com/phpstan/extension-installer/actions)
|
||||
[](https://packagist.org/packages/phpstan/extension-installer)
|
||||
[](https://packagist.org/packages/phpstan/extension-installer)
|
||||
|
||||
Composer plugin for automatic installation of [PHPStan](https://phpstan.org/) extensions.
|
||||
|
||||
# Motivation
|
||||
|
||||
```diff
|
||||
diff --git a/phpstan.neon b/phpstan.neon
|
||||
index db4e3df32e..2ca30fa20a 100644
|
||||
--- a/phpstan.neon
|
||||
+++ b/phpstan.neon
|
||||
@@ -1,12 +1,3 @@
|
||||
-includes:
|
||||
- - vendor/phpstan/phpstan-doctrine/extension.neon
|
||||
- - vendor/phpstan/phpstan-doctrine/rules.neon
|
||||
- - vendor/phpstan/phpstan-nette/extension.neon
|
||||
- - vendor/phpstan/phpstan-nette/rules.neon
|
||||
- - vendor/phpstan/phpstan-phpunit/extension.neon
|
||||
- - vendor/phpstan/phpstan-phpunit/rules.neon
|
||||
- - vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||
-
|
||||
parameters:
|
||||
autoload_directories:
|
||||
- %rootDir%/../../../build/SlevomatSniffs
|
||||
diff --git a/composer.json b/composer.json
|
||||
index 1b578dd624..f6ebf6e477 100644
|
||||
--- a/composer.json
|
||||
+++ b/composer.json
|
||||
@@ -142,6 +142,7 @@
|
||||
"jakub-onderka/php-parallel-lint": "1.0.0",
|
||||
"justinrainbow/json-schema": "5.2.8",
|
||||
"ondrejmirtes/mocktainer": "0.8",
|
||||
+ "phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "^0.11.7",
|
||||
"phpstan/phpstan-doctrine": "^0.11.3",
|
||||
"phpstan/phpstan-nette": "^0.11.1",
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
composer require --dev phpstan/extension-installer
|
||||
```
|
||||
|
||||
Starting from Composer 2.2.0 you'll get the following question:
|
||||
```
|
||||
phpstan/extension-installer contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
|
||||
Do you trust "phpstan/extension-installer" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?]
|
||||
```
|
||||
|
||||
Answer with `y` to allow the plugin.
|
||||
|
||||
## Instructions for extension developers
|
||||
|
||||
It's best (but optional) to set the extension's composer package [type](https://getcomposer.org/doc/04-schema.md#type) to `phpstan-extension` for this plugin to be able to recognize it and to be [discoverable on Packagist](https://packagist.org/explore/?type=phpstan-extension).
|
||||
|
||||
Add `phpstan` key in the extension `composer.json`'s `extra` section:
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ignoring a particular extension
|
||||
|
||||
You may want to disable auto-installation of a particular extension to handle installation manually. Ignore an extension by adding an `extra.phpstan/extension-installer.ignore` array in `composer.json` that specifies a list of packages to ignore:
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"phpstan/extension-installer": {
|
||||
"ignore": [
|
||||
"phpstan/phpstan-phpunit"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
The extension installer depends on Composer script events, therefore you cannot use `--no-scripts` flag.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "phpstan/extension-installer",
|
||||
"type": "composer-plugin",
|
||||
"description": "Composer plugin for automatic installation of PHPStan extensions",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"keywords": ["dev", "static analysis"],
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"composer-plugin-api": "^2.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"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"ocramius/package-versions": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "PHPStan\\ExtensionInstaller\\Plugin"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\ExtensionInstaller\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace PHPStan\ExtensionInstaller;
|
||||
|
||||
/**
|
||||
* This class is generated by phpstan/extension-installer.
|
||||
* @internal
|
||||
*/
|
||||
final class GeneratedConfig
|
||||
{
|
||||
|
||||
public const EXTENSIONS = array (
|
||||
'ergebnis/phpstan-rules' =>
|
||||
array (
|
||||
'install_path' => '/usr/local/src/exporter/tools/.phpstan/vendor/ergebnis/phpstan-rules',
|
||||
'relative_install_path' => '../../../ergebnis/phpstan-rules',
|
||||
'extra' =>
|
||||
array (
|
||||
'includes' =>
|
||||
array (
|
||||
0 => 'rules.neon',
|
||||
),
|
||||
),
|
||||
'version' => '2.13.1',
|
||||
'phpstanVersionConstraint' => '>=2.1.35.0-dev, <3.0.0.0-dev',
|
||||
),
|
||||
'phpstan/phpstan-strict-rules' =>
|
||||
array (
|
||||
'install_path' => '/usr/local/src/exporter/tools/.phpstan/vendor/phpstan/phpstan-strict-rules',
|
||||
'relative_install_path' => '../../phpstan-strict-rules',
|
||||
'extra' =>
|
||||
array (
|
||||
'includes' =>
|
||||
array (
|
||||
0 => 'rules.neon',
|
||||
),
|
||||
),
|
||||
'version' => '2.0.11',
|
||||
'phpstanVersionConstraint' => '>=2.1.39.0-dev, <3.0.0.0-dev',
|
||||
),
|
||||
'tomasvotruba/type-coverage' =>
|
||||
array (
|
||||
'install_path' => '/usr/local/src/exporter/tools/.phpstan/vendor/tomasvotruba/type-coverage',
|
||||
'relative_install_path' => '../../../tomasvotruba/type-coverage',
|
||||
'extra' =>
|
||||
array (
|
||||
'includes' =>
|
||||
array (
|
||||
0 => 'config/extension.neon',
|
||||
),
|
||||
),
|
||||
'version' => '2.1.0',
|
||||
'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.1.39.0-dev, <3.0.0.0-dev';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace PHPStan\ExtensionInstaller;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Composer\Semver\Constraint\MultiConstraint;
|
||||
use Composer\Semver\Intervals;
|
||||
use Composer\Util\Filesystem;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function file_exists;
|
||||
use function file_put_contents;
|
||||
use function getcwd;
|
||||
use function in_array;
|
||||
use function is_file;
|
||||
use function ksort;
|
||||
use function md5;
|
||||
use function md5_file;
|
||||
use function sort;
|
||||
use function sprintf;
|
||||
use function strpos;
|
||||
use function var_export;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
final class Plugin implements PluginInterface, EventSubscriberInterface
|
||||
{
|
||||
|
||||
/** @var string */
|
||||
private static $generatedFileTemplate = <<<'PHP'
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace PHPStan\ExtensionInstaller;
|
||||
|
||||
/**
|
||||
* This class is generated by phpstan/extension-installer.
|
||||
* @internal
|
||||
*/
|
||||
final class GeneratedConfig
|
||||
{
|
||||
|
||||
public const EXTENSIONS = %s;
|
||||
|
||||
public const NOT_INSTALLED = %s;
|
||||
|
||||
/** @var string|null */
|
||||
public const PHPSTAN_VERSION_CONSTRAINT = %s;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PHP;
|
||||
|
||||
public function activate(Composer $composer, IOInterface $io): void
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io): void
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io): void
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
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('<info>phpstan/extension-installer:</info> 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('<info>phpstan/extension-installer:</info> Extensions installed');
|
||||
|
||||
if ($oldGeneratedConfigFileHash === md5($generatedConfigFileContents)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (array_keys($installedPackages) as $name) {
|
||||
$io->write(sprintf('> <info>%s:</info> installed', $name));
|
||||
}
|
||||
|
||||
foreach (array_keys($notInstalledPackages) as $name) {
|
||||
$io->write(sprintf('> <comment>%s:</comment> not supported', $name));
|
||||
}
|
||||
|
||||
foreach ($ignoredPackages as $name) {
|
||||
$io->write(sprintf('> <comment>%s:</comment> 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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue