From 3ab3fefdd010cda553d3cc85a7d1fc144caa904c Mon Sep 17 00:00:00 2001 From: Tyler Stokes Date: Sun, 16 Aug 2026 11:42:41 -0700 Subject: [PATCH] Code Quality: Annotate three impure functions for PHPStan. PHPStan infers `current_user_can()`, `WP_Filesystem_Base::chmod()` and `WP_HTML_Processor::next_token()` as pure, so it remembers what each returned and reuses that value at a later call in the same scope. All three can legitimately answer differently the second time: `current_user_can()` reads the current user and applies the `user_has_cap` filter, `chmod()` changes the permissions that a following `is_writable()` reports, and `next_token()` advances the parser so the token accessors describe a different token. The annotations go on the mutating call rather than on the accessors PHPStan's tips name. `is_writable()`, `is_tag_closer()`, `expects_closer()`, `get_current_depth()` and `set_bookmark()` are read-only; what invalidates a remembered value is the mutation sitting between the two reads. This resolves 16 baselined errors across seven identifiers, among them the five `! current_user_can()` guards that re-check a capability already tested earlier in the same file, and the `is_writable()` and `copy()` re-checks that follow a `chmod()` in `copy_dir()`, `WP_Upgrader` and `Language_Pack_Upgrader`. The baselines were regenerated with: {{{ composer phpstan:baselines -- --identifier=booleanNot.alwaysTrue,booleanNot.alwaysFalse,booleanAnd.leftAlwaysTrue,booleanOr.alwaysTrue,deadCode.unreachable,if.alwaysTrue,ternary.alwaysTrue }}} Props tstokes8040. See #65817. --- .../includes/class-wp-filesystem-base.php | 2 ++ src/wp-includes/capabilities.php | 2 ++ .../html-api/class-wp-html-processor.php | 2 ++ .../baselines/booleanAnd.leftAlwaysTrue.neon | 5 ----- .../baselines/booleanNot.alwaysFalse.neon | 20 ------------------- .../baselines/booleanNot.alwaysTrue.neon | 20 ------------------- .../baselines/booleanOr.alwaysTrue.neon | 5 ----- .../baselines/deadCode.unreachable.neon | 5 ----- tests/phpstan/baselines/if.alwaysTrue.neon | 10 ---------- .../phpstan/baselines/ternary.alwaysTrue.neon | 5 ----- 10 files changed, 6 insertions(+), 70 deletions(-) diff --git a/src/wp-admin/includes/class-wp-filesystem-base.php b/src/wp-admin/includes/class-wp-filesystem-base.php index 4972c8421cf11..75ab85cd63d34 100644 --- a/src/wp-admin/includes/class-wp-filesystem-base.php +++ b/src/wp-admin/includes/class-wp-filesystem-base.php @@ -606,6 +606,8 @@ public function chgrp( $file, $group, $recursive = false ) { * @param bool $recursive Optional. If set to true, changes file permissions recursively. * Default false. * @return bool True on success, false on failure. + * + * @phpstan-impure */ public function chmod( $file, $mode = false, $recursive = false ) { return false; diff --git a/src/wp-includes/capabilities.php b/src/wp-includes/capabilities.php index 645ab5588ee5e..5815d69ba6120 100644 --- a/src/wp-includes/capabilities.php +++ b/src/wp-includes/capabilities.php @@ -909,6 +909,8 @@ function map_meta_cap( $cap, $user_id, ...$args ) { * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the current user has the given capability. If `$capability` is a meta cap and `$object_id` is * passed, whether the current user has the given meta capability for the given object. + * + * @phpstan-impure */ function current_user_can( $capability, ...$args ) { return user_can( wp_get_current_user(), $capability, ...$args ); diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index b4c590ef5141a..64b9122460af4 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -778,6 +778,8 @@ public function next_tag( $query = null ): bool { * @since 6.7.2 Refactored so subclasses may extend. * * @return bool Whether a token was parsed. + * + * @phpstan-impure */ public function next_token(): bool { return $this->next_visitable_token(); diff --git a/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon b/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon index c64a48177e085..1dfad85ba8c11 100644 --- a/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon +++ b/tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Left side of && is always true\.$#' - identifier: booleanAnd.leftAlwaysTrue - count: 1 - path: ../../../src/wp-admin/network/users.php - message: '#^Left side of && is always true\.$#' identifier: booleanAnd.leftAlwaysTrue diff --git a/tests/phpstan/baselines/booleanNot.alwaysFalse.neon b/tests/phpstan/baselines/booleanNot.alwaysFalse.neon index 57e8a715cfc8d..2f179a2b44ed0 100644 --- a/tests/phpstan/baselines/booleanNot.alwaysFalse.neon +++ b/tests/phpstan/baselines/booleanNot.alwaysFalse.neon @@ -18,26 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Negated boolean expression is always false\.$#' - identifier: booleanNot.alwaysFalse - count: 1 - path: ../../../src/wp-admin/includes/theme.php - - - message: '#^Negated boolean expression is always false\.$#' - identifier: booleanNot.alwaysFalse - count: 1 - path: ../../../src/wp-admin/link-manager.php - - - message: '#^Negated boolean expression is always false\.$#' - identifier: booleanNot.alwaysFalse - count: 2 - path: ../../../src/wp-admin/network/users.php - - - message: '#^Negated boolean expression is always false\.$#' - identifier: booleanNot.alwaysFalse - count: 1 - path: ../../../src/wp-admin/plugins.php - message: '#^Negated boolean expression is always false\.$#' identifier: booleanNot.alwaysFalse diff --git a/tests/phpstan/baselines/booleanNot.alwaysTrue.neon b/tests/phpstan/baselines/booleanNot.alwaysTrue.neon index ae0b0afe52c56..b61a98cef358e 100644 --- a/tests/phpstan/baselines/booleanNot.alwaysTrue.neon +++ b/tests/phpstan/baselines/booleanNot.alwaysTrue.neon @@ -23,31 +23,11 @@ parameters: identifier: booleanNot.alwaysTrue count: 1 path: ../../../src/wp-admin/includes/class-custom-image-header.php - - - message: '#^Negated boolean expression is always true\.$#' - identifier: booleanNot.alwaysTrue - count: 1 - path: ../../../src/wp-admin/includes/class-language-pack-upgrader.php - - - message: '#^Negated boolean expression is always true\.$#' - identifier: booleanNot.alwaysTrue - count: 1 - path: ../../../src/wp-admin/includes/class-wp-upgrader.php - - - message: '#^Negated boolean expression is always true\.$#' - identifier: booleanNot.alwaysTrue - count: 1 - path: ../../../src/wp-admin/includes/file.php - message: '#^Negated boolean expression is always true\.$#' identifier: booleanNot.alwaysTrue count: 1 path: ../../../src/wp-includes/class-wp-block-templates-registry.php - - - message: '#^Negated boolean expression is always true\.$#' - identifier: booleanNot.alwaysTrue - count: 1 - path: ../../../src/wp-includes/class-wp-block.php - message: '#^Negated boolean expression is always true\.$#' identifier: booleanNot.alwaysTrue diff --git a/tests/phpstan/baselines/booleanOr.alwaysTrue.neon b/tests/phpstan/baselines/booleanOr.alwaysTrue.neon index 6ee9845afe5be..e3f8f9525267f 100644 --- a/tests/phpstan/baselines/booleanOr.alwaysTrue.neon +++ b/tests/phpstan/baselines/booleanOr.alwaysTrue.neon @@ -23,8 +23,3 @@ parameters: identifier: booleanOr.alwaysTrue count: 1 path: ../../../src/wp-includes/block-supports/position.php - - - message: '#^Result of \|\| is always true\.$#' - identifier: booleanOr.alwaysTrue - count: 1 - path: ../../../src/wp-includes/class-wp-block.php diff --git a/tests/phpstan/baselines/deadCode.unreachable.neon b/tests/phpstan/baselines/deadCode.unreachable.neon index 0bd31fe524a9a..4fe379f674124 100644 --- a/tests/phpstan/baselines/deadCode.unreachable.neon +++ b/tests/phpstan/baselines/deadCode.unreachable.neon @@ -278,11 +278,6 @@ parameters: identifier: deadCode.unreachable count: 1 path: ../../../src/wp-includes/capabilities.php - - - message: '#^Unreachable statement \- code above always terminates\.$#' - identifier: deadCode.unreachable - count: 1 - path: ../../../src/wp-includes/class-wp-block.php - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable diff --git a/tests/phpstan/baselines/if.alwaysTrue.neon b/tests/phpstan/baselines/if.alwaysTrue.neon index f049efde2d19d..0545f627061d4 100644 --- a/tests/phpstan/baselines/if.alwaysTrue.neon +++ b/tests/phpstan/baselines/if.alwaysTrue.neon @@ -18,16 +18,6 @@ parameters: ignoreErrors: - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 1 - path: ../../../src/wp-admin/my-sites.php - - - message: '#^If condition is always true\.$#' - identifier: if.alwaysTrue - count: 2 - path: ../../../src/wp-admin/upload.php - message: '#^If condition is always true\.$#' identifier: if.alwaysTrue diff --git a/tests/phpstan/baselines/ternary.alwaysTrue.neon b/tests/phpstan/baselines/ternary.alwaysTrue.neon index 295254051f683..1f96f3ac23c46 100644 --- a/tests/phpstan/baselines/ternary.alwaysTrue.neon +++ b/tests/phpstan/baselines/ternary.alwaysTrue.neon @@ -23,8 +23,3 @@ parameters: identifier: ternary.alwaysTrue count: 1 path: ../../../src/wp-admin/menu-header.php - - - message: '#^Ternary operator condition is always true\.$#' - identifier: ternary.alwaysTrue - count: 1 - path: ../../../src/wp-admin/theme-install.php