Skip to content

Add initial PHPStan config#154

Open
swissspidy wants to merge 10 commits intomainfrom
fix/phpstan
Open

Add initial PHPStan config#154
swissspidy wants to merge 10 commits intomainfrom
fix/phpstan

Conversation

@swissspidy
Copy link
Copy Markdown
Member

No description provided.

@github-actions

This comment was marked as resolved.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 2, 2026

Codecov Report

❌ Patch coverage is 40.29851% with 120 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
inc/RestCommand.php 38.66% 92 Missing ⚠️
inc/Runner.php 45.09% 28 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a PHPStan configuration and adds comprehensive PHPDoc type hints to improve static analysis. Key changes include refactoring the diff_items logic to be more explicit, replacing manual WP_INSTALLING constant checks with the wp_installing() function, and correcting the casing of the RestCommand class instantiation. A regression was identified in inc/RestCommand.php where removing the reference operator from the $assoc_args parameter in get_formatter prevents the Formatter from correctly modifying the arguments array as expected by WP-CLI.

* @return \WP_CLI\Formatter
*/
protected function get_formatter( &$assoc_args ) {
protected function get_formatter( $assoc_args ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The $assoc_args parameter should be passed by reference. In WP-CLI, the Formatter class constructor expects a reference to the associative arguments array and may modify it (e.g., to remove formatting-specific flags so they aren't processed as data fields). By removing the reference here, get_formatter passes a local copy to the Formatter, which breaks the expected behavior where the Formatter can update the caller's arguments array.

protected function get_formatter( &$assoc_args ) {

@swissspidy swissspidy marked this pull request as ready for review April 7, 2026 10:32
@swissspidy swissspidy requested a review from a team as a code owner April 7, 2026 10:32
Copilot AI review requested due to automatic review settings April 7, 2026 10:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an initial PHPStan configuration for the package and updates REST CLI runtime code with additional type checks/guards to satisfy higher static-analysis strictness.

Changes:

  • Add phpstan.neon.dist with level 9 analysis targeting inc/ and wp-rest-cli.php, scanning WP-CLI sources and WordPress stubs.
  • Harden Runner route discovery/registration with additional validation and updated debug/error messaging.
  • Add/adjust phpdoc types and runtime guards in RestCommand for safer handling of decoded JSON, CLI args, and REST responses.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
phpstan.neon.dist Introduces baseline PHPStan configuration for the project.
inc/Runner.php Adds stricter validation/typing around REST index parsing and route registration.
inc/RestCommand.php Adds extensive type guards and phpdoc to reduce PHPStan findings and avoid notices.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

$name = $route_data['schema']['title'];
$rest_command = new RESTCommand( $name, $route, $route_data['schema'] );
if ( empty( $route_data['schema']['title'] ) || ! is_string( $route_data['schema']['title'] ) ) {
WP_CLI::debug( "No valid schema title found for {$route}, skipping REST command registration.", 'rest' );
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The debug message text changed from "No schema title found" to "No valid schema title found". This string is referenced in existing Behat coverage (features/load-rest.feature) and changing it will break that expectation and any tooling that greps debug logs; consider keeping the original wording (and still validate the type) or updating tests in the same PR.

Suggested change
WP_CLI::debug( "No valid schema title found for {$route}, skipping REST command registration.", 'rest' );
WP_CLI::debug( "No schema title found for {$route}, skipping REST command registration.", 'rest' );

Copilot uses AI. Check for mistakes.
}
return json_decode( $response->body, true );
$index = json_decode( $response->body, true );
/** @var array<string, mixed>|false $index */
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

get_api_index() declares it returns array<string, mixed>|false, but json_decode() can also return null/scalar for invalid/unexpected JSON. Consider normalizing the decoded value (e.g., return false unless it’s an array) or widening the @return type so callers/static analysis aren’t misled.

Suggested change
/** @var array<string, mixed>|false $index */
if ( ! is_array( $index ) ) {
return false;
}

Copilot uses AI. Check for mistakes.
Comment on lines +111 to 121
$count = 0;
if ( isset( $assoc_args['count'] ) ) {
$count = is_numeric( $assoc_args['count'] ) ? (int) $assoc_args['count'] : 0;
}
unset( $assoc_args['count'] );
$format = $assoc_args['format'];

$format = 'ids';
if ( isset( $assoc_args['format'] ) ) {
$format = is_scalar( $assoc_args['format'] ) ? (string) $assoc_args['format'] : 'ids';
}
unset( $assoc_args['format'] );
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

generate_items() falls back to count=0 and format='ids' when assoc args are missing, but the command synopsis in Runner::register_route_commands() documents defaults of count=10 and format='progress'. Align these defaults (or explicitly rely on WP-CLI to inject synopsis defaults) to avoid surprising behavior when options aren’t provided.

Copilot uses AI. Check for mistakes.
Comment on lines +328 to +333
$php_bin = WP_CLI::get_php_binary();
$argv = isset( $GLOBALS['argv'] ) && is_array( $GLOBALS['argv'] ) ? $GLOBALS['argv'] : array();
$script_path = '';
if ( isset( $argv[0] ) && is_string( $argv[0] ) ) {
$script_path = $argv[0];
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

If $GLOBALS['argv'][0] is missing or non-string, $script_path becomes an empty string and the spawned command will end up treating the next token as the script file (breaking diff). It would be safer to hard-fail with a clear error when the script path can’t be determined instead of attempting to run with an empty script.

Suggested change
$php_bin = WP_CLI::get_php_binary();
$argv = isset( $GLOBALS['argv'] ) && is_array( $GLOBALS['argv'] ) ? $GLOBALS['argv'] : array();
$script_path = '';
if ( isset( $argv[0] ) && is_string( $argv[0] ) ) {
$script_path = $argv[0];
}
$php_bin = WP_CLI::get_php_binary();
$argv = isset( $GLOBALS['argv'] ) && is_array( $GLOBALS['argv'] ) ? $GLOBALS['argv'] : array();
if ( ! isset( $argv[0] ) || ! is_string( $argv[0] ) || '' === $argv[0] ) {
WP_CLI::error( 'Unable to determine the current WP-CLI script path for spawning the diff command.' );
}
$script_path = $argv[0];

Copilot uses AI. Check for mistakes.
}
}
assert( is_int( $response->status_code ) );
return array( $response->status_code, json_decode( $response->body, true ), $response->headers->getAll() );
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

In the HTTP branch, the response body is json_decoded into $body (and normalized to an array) but the return statement json_decodes again and returns that second result. This both double-parses JSON and discards the normalized $body; consider returning the already-decoded $body to avoid extra work and keep behavior consistent.

Suggested change
return array( $response->status_code, json_decode( $response->body, true ), $response->headers->getAll() );
return array( $response->status_code, $body, $response->headers->getAll() );

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants