From 66bc551d505ae1748cb5adbf368c4602a84fbd21 Mon Sep 17 00:00:00 2001 From: Chauncey McAskill Date: Tue, 15 Aug 2023 22:19:03 -0400 Subject: [PATCH 1/3] Fix "Custom hooks" heading in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f0b9e0..2eef08b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ If you do not want to use the default WordPress hooks, which are part of this pl ``` -### Custom stubs +### Custom hooks You can also provide custom hooks: From bd97269f3bdb97a63b42b12778de50971dbf3c69 Mon Sep 17 00:00:00 2001 From: Chauncey McAskill Date: Mon, 14 Nov 2022 13:51:52 -0500 Subject: [PATCH 2/3] Update stub overrides Updated overrides to reflect WordPress 6.3. Changed: - Added typing to global constants to a dedicated 'stubs/globals.php' file. - Removed `wp_upload_dir()` and `wp_get_attachment_metadata()` as now correctly hinted in php-stubs/wordpress-stubs. - Removed `add_filter()` and `add_action()` as extraneous. - Fixed missing parameter of `wp_parse_url()` with a conditional template type (resolves #38). - Expanded array shapes over multiple lines to improve readability. --- Plugin.php | 1 + README.md | 2 +- phpcs.xml.dist | 1 + stubs/globals.php | 55 +++++++++++++ stubs/overrides.php | 184 +++++++++++++++++++++++++------------------- test.php | 8 ++ 6 files changed, 172 insertions(+), 79 deletions(-) create mode 100644 stubs/globals.php diff --git a/Plugin.php b/Plugin.php index 693fb6e..2037b00 100644 --- a/Plugin.php +++ b/Plugin.php @@ -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', ]; } diff --git a/README.md b/README.md index 2eef08b..94649ca 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 100292a..96aa5c1 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -1,6 +1,7 @@ Plugin.php + stubs tests diff --git a/stubs/globals.php b/stubs/globals.php new file mode 100644 index 0000000..e720962 --- /dev/null +++ b/stubs/globals.php @@ -0,0 +1,55 @@ + $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_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_all */ $ep_all = EP_PERMALINK | EP_ATTACHMENT | EP_ROOT | EP_COMMENTS | EP_SEARCH | EP_PAGES | EP_ALL_ARCHIVES ); diff --git a/stubs/overrides.php b/stubs/overrides.php index 353f60e..a024ebe 100644 --- a/stubs/overrides.php +++ b/stubs/overrides.php @@ -1,142 +1,172 @@ |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> - * : ( $object is 'ARRAY_A' + * : ( TObject is ARRAY_A * ? list> - * : ( $object is 'ARRAY_N' - * ? list> - * : array> + * : ( TObject is ARRAY_N + * ? list> + * : array> * ) - * ) - * ) + * ) + * )|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, 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 + * `
` and `

` tags). + * @type string $type Optional. Message type, controls HTML class. Possible values include 'error', + * 'success', 'warning', 'info'. Default 'error'. + * } + * } + * @psalm-return array + */ +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 - * @template Defaults of array - * @psalm-param Args $args - * @psalm-param Defaults $defaults - * @psalm-return Defaults&Args + * @template TArgs of array + * @template TDefaults of array + * @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 @@ -145,6 +175,4 @@ function is_wp_error( $error ) : bool { * @param K $column * @return list */ -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 {} diff --git a/test.php b/test.php index 16826e0..88c6c34 100644 --- a/test.php +++ b/test.php @@ -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' ); From 9f4fa7c34c9d4ddec14b8015f6a341d3ededd1d8 Mon Sep 17 00:00:00 2001 From: Chauncey McAskill Date: Tue, 15 Aug 2023 22:36:25 -0400 Subject: [PATCH 3/3] Add section to README about WP global constants Global constants such as those for paths can't be stubbed because they need to be literal string paths to the actual locations of included/required files. --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 94649ca..89d44c0 100644 --- a/README.md +++ b/README.md @@ -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 + + + + +``` + +The following example bootstrap file is for a Bedrock installation: + +```php +