Skip to content

Commit

Permalink
Merge pull request #44 from psalm/feature/update-stubs-overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
mcaskill authored Aug 16, 2023
2 parents 7571403 + 9f4fa7c commit c02725c
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 80 deletions.
1 change: 1 addition & 0 deletions Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ private function getStubFiles() : array {
self::getVendorDir( 'vendor/php-stubs/wp-cli-stubs' ) . '/wp-cli-stubs.php',
self::getVendorDir( 'vendor/php-stubs/wp-cli-stubs' ) . '/wp-cli-commands-stubs.php',
self::getVendorDir( 'vendor/php-stubs/wp-cli-stubs' ) . '/wp-cli-i18n-stubs.php',
__DIR__ . '/stubs/globals.php',
__DIR__ . '/stubs/overrides.php',
];
}
Expand Down
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Further details about plugins can be found on [Psalm's website](https://psalm.de

### Default WordPress stubs

If you do not want to use the default WordPress stubs, which are part of this plugin, `useDefaultStubs` must be set to `false`:
If you do not want to use the default WordPress class/method/function stubs, which are part of this plugin, `useDefaultStubs` must be set to `false`:

```xml
<pluginClass class="PsalmWordPress\Plugin">
Expand All @@ -61,7 +61,7 @@ If you do not want to use the default WordPress hooks, which are part of this pl
</pluginClass>
```

### Custom stubs
### Custom hooks

You can also provide custom hooks:

Expand All @@ -83,6 +83,30 @@ If a directory is provided, the plugin will search for the following files:

The plugin expects a JSON representation of the hooks as per [wp-hooks/generator](https://github.com/wp-hooks/generator).

### WordPress paths

To help Psalm analyze your project you might need to define some of WordPress' default global constants such as those for paths.

```xml
<?xml version="1.0"?>
<psalm autoloader="tests/bootstrap.php" xmlns="https://getpsalm.org/schema/config">
<!-- project configuration -->
</psalm>
```

The following example bootstrap file is for a Bedrock installation:

```php
<?php

require_once dirname( __DIR__ ) . '/config/application.php';

define( 'WPMU_PLUGIN_DIR', WP_CONTENT_DIR . '/mu-plugins' );

define( 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins' );
```

You could require WordPress' default constants functions but that requires a lot more boilerplating to allow those functions to effectively define constants.

## Interested in contributing?

Expand Down
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0"?>
<ruleset name="PsalmWordPress">
<file>Plugin.php</file>
<file>stubs</file>
<file>tests</file>

<rule ref="HM">
Expand Down
55 changes: 55 additions & 0 deletions stubs/globals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

// phpcs:disable Squiz.PHP.DiscouragedFunctions,NeutronStandard.Constants.DisallowDefine

// ./wp-includes/default-constants.php

define( 'WP_DEBUG', /** @var bool $wp_debug */ $wp_debug = true );
define( 'WP_DEBUG_LOG', /** @var bool $wp_debug_log */ $wp_debug_log = true );

define( 'EMPTY_TRASH_DAYS', /** @var int<0, max> $empty_trash_days */ $empty_trash_days = 30 );

define( 'MINUTE_IN_SECONDS', /** @var 60 $minute_in_seconds */ $minute_in_seconds = 60 );
define( 'HOUR_IN_SECONDS', /** @var 3600 $hour_in_seconds */ $hour_in_seconds = 60 * MINUTE_IN_SECONDS );
define( 'DAY_IN_SECONDS', /** @var 86400 $day_in_seconds */ $day_in_seconds = 24 * HOUR_IN_SECONDS );
define( 'WEEK_IN_SECONDS', /** @var 604800 $week_in_seconds */ $week_in_seconds = 7 * DAY_IN_SECONDS );
define( 'MONTH_IN_SECONDS', /** @var 2592000 $month_in_seconds */ $month_in_seconds = 30 * DAY_IN_SECONDS );
define( 'YEAR_IN_SECONDS', /** @var 31536000 $year_in_seconds */ $year_in_seconds = 365 * DAY_IN_SECONDS );

define( 'KB_IN_BYTES', /** @var 1024 $kb_in_bytes */ $kb_in_bytes = 1024 );
define( 'MB_IN_BYTES', /** @var 1048576 $mb_in_bytes */ $mb_in_bytes = 1024 * KB_IN_BYTES );
define( 'GB_IN_BYTES', /** @var 1073741824 $gb_in_bytes */ $gb_in_bytes = 1024 * MB_IN_BYTES );
define( 'TB_IN_BYTES', /** @var 1099511627776 $tb_in_bytes */ $tb_in_bytes = 1024 * GB_IN_BYTES );

// ./wp-includes/wp-db.php

define( 'OBJECT', /** @var 'OBJECT' $object */ $object = 'OBJECT' );
define( 'OBJECT_K', /** @var 'OBJECT_K' $object_k */ $object_k = 'OBJECT_K' );
define( 'ARRAY_A', /** @var 'ARRAY_A' $array_a */ $array_a = 'ARRAY_A' );
define( 'ARRAY_N', /** @var 'ARRAY_N' $array_n */ $array_n = 'ARRAY_N' );

// ./wp-admin/includes/file.php

define( 'FS_CONNECT_TIMEOUT', /** @var int<0, max> $fs_connect_timeout */ $fs_connect_timeout = 30 );
define( 'FS_TIMEOUT', /** @var int<0, max> $fs_timeout */ $fs_timeout = 30 );
define( 'FS_CHMOD_DIR', /** @var int $fs_chmod_dir */ $fs_chmod_dir = 0755 );
define( 'FS_CHMOD_FILE', /** @var int $fs_chmod_file */ $fs_chmod_file = 0644 );

// ./wp-includes/rewrite.php

define( 'EP_NONE', /** @var 0 $ep_none */ $ep_none = 0 );
define( 'EP_PERMALINK', /** @var 1 $ep_permalink */ $ep_permalink = 1 );
define( 'EP_ATTACHMENT', /** @var 2 $ep_attachment */ $ep_attachment = 2 );
define( 'EP_DATE', /** @var 4 $ep_date */ $ep_date = 4 );
define( 'EP_YEAR', /** @var 8 $ep_year */ $ep_year = 8 );
define( 'EP_MONTH', /** @var 16 $ep_month */ $ep_month = 16 );
define( 'EP_DAY', /** @var 32 $ep_day */ $ep_day = 32 );
define( 'EP_ROOT', /** @var 64 $ep_root */ $ep_root = 64 );
define( 'EP_COMMENTS', /** @var 128 $ep_comments */ $ep_comments = 128 );
define( 'EP_SEARCH', /** @var 256 $ep_search */ $ep_search = 256 );
define( 'EP_CATEGORIES', /** @var 512 $ep_categories */ $ep_categories = 512 );
define( 'EP_TAGS', /** @var 1024 $ep_tags */ $ep_tags = 1024 );
define( 'EP_AUTHORS', /** @var 2048 $ep_authors */ $ep_authors = 2048 );
define( 'EP_PAGES', /** @var 4096 $ep_pages */ $ep_pages = 4096 );
define( 'EP_ALL_ARCHIVES', /** @var int-mask<EP_DATE|EP_YEAR|EP_MONTH|EP_DAY|EP_CATEGORIES|EP_TAGS|EP_AUTHORS> $ep_all_archives */ $ep_all_archives = EP_DATE | EP_YEAR | EP_MONTH | EP_DAY | EP_CATEGORIES | EP_TAGS | EP_AUTHORS );
define( 'EP_ALL', /** @var int-mask<EP_PERMALINK|EP_ATTACHMENT|EP_ROOT|EP_COMMENTS|EP_SEARCH|EP_PAGES|EP_ALL_ARCHIVES> $ep_all */ $ep_all = EP_PERMALINK | EP_ATTACHMENT | EP_ROOT | EP_COMMENTS | EP_SEARCH | EP_PAGES | EP_ALL_ARCHIVES );
184 changes: 106 additions & 78 deletions stubs/overrides.php
Original file line number Diff line number Diff line change
@@ -1,142 +1,172 @@
<?php

define( 'HOUR_IN_SECONDS', 3600 );
// phpcs:disable HM.Functions.NamespacedFunctions.MissingNamespace,PEAR.NamingConventions.ValidClassName.StartWithCapital,PSR1.Classes.ClassDeclaration.MissingNamespace,Squiz.Commenting.FunctionComment.InvalidNoReturn,Squiz.Commenting.FunctionComment.MissingParamName

class WP_CLI {
/**
*
* @param string $command
* @param callable|class-string $class
* @param array{before_invoke?: callable, after_invoke?: callable, shortdesc?: string, longdesc?: string, synopsis?: string, when?: string, is_deferred?: bool} $args
* @param string $name
* @param callable|class-string $callable
* @param array{
* before_invoke?: callable,
* after_invoke?: callable,
* shortdesc?: string,
* longdesc?: string,
* synopsis?: string,
* when?: string,
* is_deferred?: bool
* } $args
* @return bool
*/
static function add_command( string $command, $class, array $args = [] ) {

}
public static function add_command( string $name, $callable, array $args = [] ) {}

/**
* @param WP_Error|string $message
* @param bool|int $exit
*
* @template TExit of bool|int
* @param string|WP_Error|Exception|Throwable $message
* @psalm-param TExit $exit
* @return (
* $exit is true
* TExit is true
* ? no-return
* : null
* )
*/
public static function error( $message, $exit = true ) {
}
public static function error( $message, $exit = true ) {}
}

class wpdb {
/**
* @param string $query
* @param array<scalar>|scalar $args
* @return string|null
* @return string|void
*/
public function prepare( $query, $args ) {
public function prepare( $query, $args ) {}

}
/**
* Undocumented function
*
* @param string? $query
* @param 'OBJECT'|'ARRAY_A'|'ARRAY_N'|'OBJECT_K' $object
* @template TObject of ARRAY_A|ARRAY_N|OBJECT|OBJECT_K
* @param string|null $query
* @psalm-param TObject $object
* @return (
* $object is 'OBJECT'
* TObject is OBJECT
* ? list<ArrayObject<string, string>>
* : ( $object is 'ARRAY_A'
* : ( TObject is ARRAY_A
* ? list<array<string, scalar>>
* : ( $object is 'ARRAY_N'
* ? list<array<scalar>>
* : array<string, ArrayObject<string, scalar>>
* : ( TObject is ARRAY_N
* ? list<array<scalar>>
* : array<string, ArrayObject<string, scalar>>
* )
* )
* )
* )
* )|null
*/
public function get_results( $query = null, $object = 'OBJECT' ) {

}
public function get_results( $query = null, $object = \OBJECT ) {}
}


/**
* @return array{path: string, basedir: string, baseurl: string, url: string}
* @return array{
* path: string,
* url: string,
* subdir: string,
* basedir: string,
* baseurl: string,
* error: string|false,
* }
*/
function wp_upload_dir() {

}

/**
* @return array{path: string, basedir: string, baseurl: string, url: string}
*/
function wp_get_upload_dir() {

}
function wp_get_upload_dir() {}

/**
* @template TFilterValue
* @param string $a
* @param string $hook_name
* @psalm-param TFilterValue $value
* @return TFilterValue
*/
function apply_filters( string $a, $value, ...$args ) {}

function add_filter( string $filter, callable $function, int $priority = 10, int $args = 1 ) {}

function add_action( string $filter, callable $function, int $priority = 10, int $args = 1 ) {}

/**
* @param integer $attachment_id
* @param boolean $skip_filters
* @return array{width?: int, height?: int, sizes?: array<string,array{width: int, height: int, file: string}>, file: string}
*/
function wp_get_attachment_metadata( int $attachment_id, $skip_filters = false ) {}
function apply_filters( string $hook_name, $value, ...$args ) {}

/**
* | Component | |
* | ---------------- | - |
* | PHP_URL_SCHEME | 0 |
* | PHP_URL_HOST | 1 |
* | PHP_URL_PORT | 2 |
* | PHP_URL_USER | 3 |
* | PHP_URL_PASS | 4 |
* | PHP_URL_PATH | 5 |
* | PHP_URL_QUERY | 6 |
* | PHP_URL_FRAGMENT | 7 |
*
* @template TComponent of (-1|PHP_URL_*)
* @param string $url
* @return array{path?: string, scheme?: string, host?: string, port?: int, user?: string, pass?: string, query?: string, fragment?: string}
* @param TComponent $component
* @return (
* TComponent is -1
* ? array{
* scheme?: string,
* host?: string,
* port?: int,
* user?: string,
* pass?: string,
* path?: string,
* query?: string,
* fragment?: string,
* }
* : (
* TComponent is 2
* ? int|null
* : string|null
* )
* )|false
*/
function wp_parse_url( string $url ) {}
function wp_parse_url( string $url, int $component = -1 ) {}

/**
*
* @param string $option
* @param mixed $default
* @return mixed
*/
function get_option( string $option, $default = null ) {}


/**
* @return array[] {
* Array of settings error arrays.
*
* @type array ...$0 {
* Associative array of setting error data.
*
* @type string $setting Slug title of the setting to which this error applies.
* @type string $code Slug-name to identify the error. Used as part of 'id' attribute in HTML output.
* @type string $message The formatted message text to display to the user (will be shown inside styled
* `<div>` and `<p>` tags).
* @type string $type Optional. Message type, controls HTML class. Possible values include 'error',
* 'success', 'warning', 'info'. Default 'error'.
* }
* }
* @psalm-return array<int|string, array{
* setting: string,
* code: string,
* message: string,
* type: string,
* }>
*/
function get_settings_errors( $setting = '', $sanitize = false ) : array {}

/**
* @param string $path
* @param "https"|"http"|"relative"|"rest" $scheme
* @param 'https'|'http'|'relative'|'rest' $scheme
* @return string
*/
function home_url( string $path = null, $scheme = null ) : string {

}
function home_url( string $path = '', $scheme = null ) : string {}

/**
*
* @template Args of array<array-key, mixed>
* @template Defaults of array<array-key, mixed>
* @psalm-param Args $args
* @psalm-param Defaults $defaults
* @psalm-return Defaults&Args
* @template TArgs of array<array-key, mixed>
* @template TDefaults of array<array-key, mixed>
* @psalm-param TArgs $args
* @psalm-param TDefaults $defaults
* @psalm-return TDefaults&TArgs
*/
function wp_parse_args( $args, $defaults ) {
}
function wp_parse_args( $args, $defaults ) {}

/**
* @param WP_Error|mixed $error
* @psalm-assert-if-true WP_Error $error
*/
function is_wp_error( $error ) : bool {

}
function is_wp_error( $error ) : bool {}

/**
* @template T
Expand All @@ -145,6 +175,4 @@ function is_wp_error( $error ) : bool {
* @param K $column
* @return list<T>
*/
function wp_list_pluck( array $list, string $column, string $index_key = null ) : array {

}
function wp_list_pluck( array $list, string $column, string $index_key = null ) : array {}
8 changes: 8 additions & 0 deletions test.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ function filter_upload_dir( array $dir ) : array {
add_filter( 'admin_notices', function () {
echo 'hi';
} );

$uploads = wp_get_upload_dir();

$url_host = wp_parse_url( 'https://github.com:443/psalm/psalm-plugin-wordpress?query=1#frag', PHP_URL_HOST );

$url_port = wp_parse_url( 'https://github.com:443/psalm/psalm-plugin-wordpress?query=1#frag', PHP_URL_PORT );

$url_parts = wp_parse_url( 'https://github.com:443/psalm/psalm-plugin-wordpress?query=1#frag' );

0 comments on commit c02725c

Please sign in to comment.