diff --git a/src/Uninstaller.php b/src/Uninstaller.php index ea94628..26400b1 100644 --- a/src/Uninstaller.php +++ b/src/Uninstaller.php @@ -182,7 +182,7 @@ class Uninstaller { $sql = $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $wpdb->prefix . $table ); if ( null !== $sql ) { - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- uninstall drops the plugin's own tables; the names come from Schema::TABLES, not from input. + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- $sql is the prepared statement two lines up; the sniff cannot follow it across the null guard, which PHPStan requires because prepare() is nullable. The only interpolated value is a Schema::TABLES constant. $wpdb->query( $sql ); } } diff --git a/tests/Unit/UninstallerTest.php b/tests/Unit/UninstallerTest.php index 1e7cad4..c051d6e 100644 --- a/tests/Unit/UninstallerTest.php +++ b/tests/Unit/UninstallerTest.php @@ -41,8 +41,13 @@ class UninstallerTest extends TestCase $rolesRemoved = &$this->rolesRemoved; $hooksCleared = &$this->hooksCleared; + // A regular closure, not an arrow fn: arrow functions capture by value, + // so every read would answer from a snapshot of the options taken at + // setUp — before the test set any, and before the run wrote any. Functions\when('get_option')->alias( - static fn(string $key, mixed $default = false): mixed => $options[$key] ?? $default + static function (string $key, mixed $default = false) use (&$options): mixed { + return $options[$key] ?? $default; + } ); Functions\when('update_option')->alias( static function (string $key, mixed $value) use (&$options): bool {