Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/phpstan-static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ on:
- 'phpstan.neon.dist'
- 'tests/phpstan/base.neon'
- 'tests/phpstan/baselines/**'
# These files configure the tests of the PHPStan extensions.
- 'tests/phpstan/phpunit.xml.dist'
- 'tests/phpstan/Tests/tests.neon'
# Confirm any changes to relevant workflow files.
- '.github/workflows/phpstan-static-analysis.yml'
- '.github/workflows/reusable-phpstan-static-analysis-v1.yml'
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/reusable-phpstan-static-analysis-v1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
# - Logs debug information.
# - Installs Composer dependencies.
# - Make Composer packages available globally.
# - Runs the tests of the WordPress-specific PHPStan extensions.
# - Builds WordPress.
# - Configures caching for PHPStan static analysis scans.
# - Runs PHPStan static analysis.
Expand Down Expand Up @@ -75,6 +76,13 @@ jobs:
- name: Make Composer packages available globally
run: echo "${PWD}/vendor/bin" >> "$GITHUB_PATH"

# The extensions in `tests/phpstan` are tested against fixtures rather than against
# core, so a defect in one is reported here as a failing test rather than as a
# change in what the analysis below reports.
- name: Run the tests of the PHPStan extensions
if: ${{ hashFiles('tests/phpstan/phpunit.xml.dist') != '' }}
run: composer run phpstan:test
Comment thread
johnbillion marked this conversation as resolved.
Resolved

# The directory holds more than the analysis results. PHPStan also stores what it read out of
# each source file there, the docblocks and signatures it found, keyed by that file's contents
# and nothing else. The extensions in `tests/phpstan` change what reading a file yields without
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"scripts": {
"phpstan": "@php ./vendor/bin/phpstan analyse --memory-limit=2G",
"phpstan:baselines": [ "Composer\\Config::disableProcessTimeout", "@php ./tests/phpstan/generate-baselines.php" ],
"phpstan:test": "@php ./vendor/phpunit/phpunit/phpunit -c tests/phpstan/phpunit.xml.dist",
"compat": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --standard=phpcompat.xml.dist --report=summary,source",
"format": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf --report=summary,source",
"lint": "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs --report=summary,source",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
"test:visual": "wp-scripts test-playwright --config tests/visual-regression/playwright.config.js",
"typecheck:php": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan --",
"typecheck:php:baselines": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan:baselines --",
"typecheck:php:test": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan:test --",
"gutenberg:copy": "node tools/gutenberg/copy.js",
"gutenberg:verify": "node tools/gutenberg/utils.js",
"gutenberg:download": "node tools/gutenberg/download.js && npm run build:gutenberg"
Expand Down
18 changes: 18 additions & 0 deletions tests/phpstan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,24 @@ These identifiers are specific to WordPress, and can be ignored or baselined lik
| `wordpress.hookDocReferenceHookMissing` | The referenced file exists, but documents no hook of that name. Either the reference is stale, or the canonical docblock has moved. |
| `wordpress.hookParamCountMismatch` | The call passes a different number of arguments than the docblock documents `@param` tags for. Passing fewer risks an `ArgumentCountError` in a callback registered for the documented count; passing more silently drops the extra argument and leaves the documentation misleading. |

### Testing the extensions

The extensions have tests of their own under [`Tests/`](Tests), built on PHPStan's test harness. The two rules are run over fixture files and the errors they report are compared with the ones expected. The visitors and the return type extension are checked through the types PHPStan infers for the `assertType()` calls in their fixtures. Reference comments resolve against a small WordPress tree of fixtures in [`Tests/data/wordpress`](Tests/data/wordpress) rather than against `src`, so a broken reference can be tested without one existing in core.

Run them with:

```bash
npm run typecheck:php:test
```

or, outside the Docker environment:

```bash
composer phpstan:test
```

They also run in CI, ahead of the analysis itself.

## Ignoring and baselining errors

As we adopt PHPStan iteratively, you may be faced with false positives due to legacy code, or code that is not worth changing at this time.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* Tests for ApplyFiltersDynamicFunctionReturnTypeExtension.
*
* @package WordPress
*/

declare(strict_types=1);

namespace WordPress\PHPStan\Tests;

use PHPStan\Testing\TypeInferenceTestCase;

/**
* Checks the type PHPStan infers for a filtered value against the `assertType()`
* calls in the fixtures.
*/
final class ApplyFiltersDynamicFunctionReturnTypeExtensionTest extends TypeInferenceTestCase {

/**
* Gathers the assertions from the fixtures.
*
* @return iterable<string, mixed[]>
*/
public function data_file_asserts(): iterable {
yield from self::gatherAssertTypes( __DIR__ . '/data/apply-filters.php' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooo. Cool language feature I haven't seen before.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah unfortunately PHPUnit collects all data provider data for all tests before starting (so it can show progress) so it doesn't actually provide the memory benefit that a generator usually does. Neat though.

yield from self::gatherAssertTypes( __DIR__ . '/data/ApplyFiltersTestClass.php' );
}

/**
* @dataProvider data_file_asserts
*
* @param string $assert_type Kind of assertion.
* @param string $file Fixture file.
* @param mixed ...$args Assertion arguments.
*/
public function test_file_asserts( string $assert_type, string $file, ...$args ): void {
$this->assertFileAsserts( $assert_type, $file, ...$args );
}

/**
* Returns the configuration registering the extension and the visitors it relies on.
*
* @return list<string>
*/
public static function getAdditionalConfigFiles(): array {
return array( __DIR__ . '/tests.neon' );
}
}
49 changes: 49 additions & 0 deletions tests/phpstan/Tests/GlobalDocBlockVisitorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* Tests for GlobalDocBlockVisitor.
*
* @package WordPress
*/

declare(strict_types=1);

namespace WordPress\PHPStan\Tests;

use PHPStan\Testing\TypeInferenceTestCase;

/**
* Checks the type PHPStan infers for an imported global against the `assertType()`
* calls in the fixtures.
*/
final class GlobalDocBlockVisitorTest extends TypeInferenceTestCase {

/**
* Gathers the assertions from the fixtures.
*
* @return iterable<string, mixed[]>
*/
public function data_file_asserts(): iterable {
yield from self::gatherAssertTypes( __DIR__ . '/data/global-docblock.php' );
yield from self::gatherAssertTypes( __DIR__ . '/data/GlobalDocBlockTestClass.php' );
}

/**
* @dataProvider data_file_asserts
*
* @param string $assert_type Kind of assertion.
* @param string $file Fixture file.
* @param mixed ...$args Assertion arguments.
*/
public function test_file_asserts( string $assert_type, string $file, ...$args ): void {
$this->assertFileAsserts( $assert_type, $file, ...$args );
}

/**
* Returns the configuration registering the visitor.
*
* @return list<string>
*/
public static function getAdditionalConfigFiles(): array {
return array( __DIR__ . '/tests.neon' );
}
}
47 changes: 47 additions & 0 deletions tests/phpstan/Tests/HashNotationTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Tests the types PHPStan infers once HashNotationVisitor has translated a hash.
*
* @package WordPress
*/

declare(strict_types=1);

namespace WordPress\PHPStan\Tests;

use PHPStan\Testing\TypeInferenceTestCase;

/**
* Checks the `assertType()` calls in the hash notation fixture.
*/
final class HashNotationTypeTest extends TypeInferenceTestCase {

/**
* Gathers the assertions from the fixture.
*
* @return iterable<string, mixed[]>
*/
public function data_file_asserts(): iterable {
yield from self::gatherAssertTypes( __DIR__ . '/data/hash-notation.php' );
}

/**
* @dataProvider data_file_asserts
*
* @param string $assert_type Kind of assertion.
* @param string $file Fixture file.
* @param mixed ...$args Assertion arguments.
*/
public function test_file_asserts( string $assert_type, string $file, ...$args ): void {
$this->assertFileAsserts( $assert_type, $file, ...$args );
}

/**
* Returns the configuration registering the visitor.
*
* @return list<string>
*/
public static function getAdditionalConfigFiles(): array {
return array( __DIR__ . '/tests.neon' );
}
}
Loading
Loading