From bc8b96aabf2979bdafc98944f2a19475f19c94b3 Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Fri, 23 Jan 2026 10:58:03 -0400 Subject: [PATCH 1/6] New sniff to make type comments more accurate --- classes/controllers/FrmFieldsController.php | 6 +- .../controllers/FrmFormActionsController.php | 2 +- classes/controllers/FrmFormsController.php | 6 +- classes/controllers/FrmXMLController.php | 4 +- classes/helpers/FrmAppHelper.php | 2 +- classes/helpers/FrmEntriesHelper.php | 6 +- classes/helpers/FrmXMLHelper.php | 4 +- classes/models/FrmDb.php | 6 +- classes/models/FrmEmail.php | 2 +- classes/models/FrmEntry.php | 2 +- classes/models/FrmForm.php | 6 +- classes/models/FrmFormAction.php | 4 +- classes/models/FrmSettings.php | 6 +- .../Commenting/AddMissingParamTypeSniff.php | 410 ++++++++++++++++++ phpcs-sniffs/Formidable/ruleset.xml | 1 + 15 files changed, 439 insertions(+), 28 deletions(-) create mode 100644 phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php diff --git a/classes/controllers/FrmFieldsController.php b/classes/controllers/FrmFieldsController.php index d326cd5d9c..99d43c5b1e 100644 --- a/classes/controllers/FrmFieldsController.php +++ b/classes/controllers/FrmFieldsController.php @@ -155,9 +155,9 @@ public static function get_field_array_from_id( $field_id ) { /** * @since 3.0 * - * @param array|int|object $field_object - * @param array $values - * @param int $form_id + * @param array|int|object|string $field_object + * @param array $values + * @param int $form_id * * @return void */ diff --git a/classes/controllers/FrmFormActionsController.php b/classes/controllers/FrmFormActionsController.php index ad33f6b6e7..f24f52b5b5 100644 --- a/classes/controllers/FrmFormActionsController.php +++ b/classes/controllers/FrmFormActionsController.php @@ -639,7 +639,7 @@ public static function trigger_create_actions( $entry_id, $form_id, $args = arra /** * @param string $event * @param int|object|string $form - * @param int|string $entry + * @param int|string|object $entry * @param string $type * @param array $args * diff --git a/classes/controllers/FrmFormsController.php b/classes/controllers/FrmFormsController.php index a3205657ec..8fa889e1f2 100644 --- a/classes/controllers/FrmFormsController.php +++ b/classes/controllers/FrmFormsController.php @@ -3394,9 +3394,9 @@ private static function show_lone_success_message( $atts ) { * @since 2.05 * @since 6.0.x Added the third parameter. * - * @param object $form Form object. - * @param int $entry_id Entry ID. - * @param array $args See {@see FrmFormsController::run_success_action()}. + * @param object $form Form object. + * @param int|string $entry_id Entry ID. + * @param array $args See {@see FrmFormsController::run_success_action()}. * * @return string */ diff --git a/classes/controllers/FrmXMLController.php b/classes/controllers/FrmXMLController.php index 3208c81d9b..b15ad93dbf 100644 --- a/classes/controllers/FrmXMLController.php +++ b/classes/controllers/FrmXMLController.php @@ -443,8 +443,8 @@ public static function export_xml() { } /** - * @param array|string $type - * @param array $args + * @param array|string|array $type + * @param array $args * * @psalm-param array{ids?: mixed} $args * diff --git a/classes/helpers/FrmAppHelper.php b/classes/helpers/FrmAppHelper.php index 78851d3c0d..8df4e8607d 100644 --- a/classes/helpers/FrmAppHelper.php +++ b/classes/helpers/FrmAppHelper.php @@ -2432,7 +2432,7 @@ public static function checked( $values, $current ) { /** * @param array|string $values - * @param string $current + * @param string|null $current * * @return bool */ diff --git a/classes/helpers/FrmEntriesHelper.php b/classes/helpers/FrmEntriesHelper.php index fb81851ce2..83c642bcad 100644 --- a/classes/helpers/FrmEntriesHelper.php +++ b/classes/helpers/FrmEntriesHelper.php @@ -416,9 +416,9 @@ private static function set_parent_field_posted_value( $field, $value, $args ) { } /** - * @param array|int|object $field - * @param mixed $value - * @param array $args + * @param array|int|object|string $field + * @param mixed $value + * @param array $args * * @return void */ diff --git a/classes/helpers/FrmXMLHelper.php b/classes/helpers/FrmXMLHelper.php index b3b6922cc2..908195c2e1 100644 --- a/classes/helpers/FrmXMLHelper.php +++ b/classes/helpers/FrmXMLHelper.php @@ -329,7 +329,7 @@ private static function fill_form( $item ) { } /** - * @param array $form + * @param array|object $form * * @return false|object */ @@ -1889,7 +1889,7 @@ private static function remove_default_html( $html_name, $defaults, &$options ) } /** - * @param string $str + * @param string|array $str * * @return string */ diff --git a/classes/models/FrmDb.php b/classes/models/FrmDb.php index 876dbe2a7d..7e7968a7b4 100644 --- a/classes/models/FrmDb.php +++ b/classes/models/FrmDb.php @@ -205,9 +205,9 @@ private static function interpret_array_to_sql( $key, $value, &$where, &$values * * @since 2.02.05 * - * @param string $key - * @param int|string $value - * @param string $where + * @param string $key + * @param int|string|float $value + * @param string $where * * @return void */ diff --git a/classes/models/FrmEmail.php b/classes/models/FrmEmail.php index 854869e0a8..d52daf4b7c 100644 --- a/classes/models/FrmEmail.php +++ b/classes/models/FrmEmail.php @@ -656,7 +656,7 @@ private function prepare_email_setting( $value, $user_id_args ) { * * @since 2.03.04 * - * @param string $emails + * @param string|array $emails * * @return array|string Emails. */ diff --git a/classes/models/FrmEntry.php b/classes/models/FrmEntry.php index e9c65b7fdc..6a16db6dc1 100644 --- a/classes/models/FrmEntry.php +++ b/classes/models/FrmEntry.php @@ -462,7 +462,7 @@ public static function get_new_entry_name( $values, $default = '' ) { * * @since 2.0.9 * - * @param int|object $entry By reference. + * @param int|object|string $entry By reference. * * @return void */ diff --git a/classes/models/FrmForm.php b/classes/models/FrmForm.php index d032d8245a..47d4eb09f3 100644 --- a/classes/models/FrmForm.php +++ b/classes/models/FrmForm.php @@ -605,8 +605,8 @@ public static function translatable_strings( $form ) { } /** - * @param int $id - * @param string $status + * @param int|array $id + * @param string $status * * @return bool|int */ @@ -837,7 +837,7 @@ public static function get_key_by_id( $id ) { * * @since 2.0.9 * - * @param int|object $form + * @param int|object|array $form * * @return void */ diff --git a/classes/models/FrmFormAction.php b/classes/models/FrmFormAction.php index 8f93d76919..b10ff4ff9a 100644 --- a/classes/models/FrmFormAction.php +++ b/classes/models/FrmFormAction.php @@ -666,8 +666,8 @@ public static function get_action_limit( $form_id, $limit = 99 ) { /** * @since 3.04 * - * @param array $args - * @param string $default_status + * @param array|string $args + * @param string $default_status * * @return void */ diff --git a/classes/models/FrmSettings.php b/classes/models/FrmSettings.php index 0e8ac0a720..8f13189e21 100644 --- a/classes/models/FrmSettings.php +++ b/classes/models/FrmSettings.php @@ -633,9 +633,9 @@ private function update_roles( $params ) { * * @since 6.9 * - * @param string $key The setting key to update. - * @param mixed $value The new value for the setting. - * @param string $sanitize The name of the sanitization function to apply to the new value. + * @param string $key The setting key to update. + * @param mixed $value The new value for the setting. + * @param string|callable $sanitize The name of the sanitization function to apply to the new value. * * @return bool True on success, false on failure. */ diff --git a/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php new file mode 100644 index 0000000000..cdf3073663 --- /dev/null +++ b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php @@ -0,0 +1,410 @@ + 'array', + 'is_object' => 'object', + 'is_int' => 'int', + 'is_integer' => 'int', + 'is_long' => 'int', + 'is_float' => 'float', + 'is_double' => 'float', + 'is_real' => 'float', + 'is_string' => 'string', + 'is_bool' => 'bool', + 'is_null' => 'null', + 'is_numeric' => 'string', + 'is_callable' => 'callable', + 'is_iterable' => 'iterable', + 'is_resource' => 'resource', + ); + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + */ + public function register() { + return array( T_FUNCTION ); + } + + /** + * Processes this test, when one of its tokens is encountered. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the current token in the stack passed in $tokens. + * + * @return void + */ + public function process( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // Skip if no scope (abstract method, interface method). + if ( ! isset( $tokens[ $stackPtr ]['scope_opener'] ) || ! isset( $tokens[ $stackPtr ]['scope_closer'] ) ) { + return; + } + + // Check if function has a docblock. + $docblock = $this->findDocblock( $phpcsFile, $stackPtr ); + + if ( false === $docblock ) { + return; + } + + // Get function parameters. + $params = $this->getParameters( $phpcsFile, $stackPtr ); + + if ( empty( $params ) ) { + return; + } + + // Get existing @param tags with their types. + $existingParams = $this->getExistingParamTags( $phpcsFile, $docblock ); + + if ( empty( $existingParams ) ) { + return; + } + + $scopeOpener = $tokens[ $stackPtr ]['scope_opener']; + $scopeCloser = $tokens[ $stackPtr ]['scope_closer']; + + // Find is_* checks for each parameter. + $missingTypes = array(); + + foreach ( $params as $param ) { + $paramName = $param['name']; + + if ( ! isset( $existingParams[ $paramName ] ) ) { + continue; + } + + $existingType = $existingParams[ $paramName ]['type']; + $checkedTypes = $this->findIsChecksForParam( $phpcsFile, $paramName, $scopeOpener, $scopeCloser ); + + foreach ( $checkedTypes as $checkedType ) { + if ( ! $this->typeIncludesType( $existingType, $checkedType ) ) { + if ( ! isset( $missingTypes[ $paramName ] ) ) { + $missingTypes[ $paramName ] = array( + 'existing' => $existingType, + 'missing' => array(), + 'token' => $existingParams[ $paramName ]['token'], + ); + } + $missingTypes[ $paramName ]['missing'][] = $checkedType; + } + } + } + + if ( empty( $missingTypes ) ) { + return; + } + + // Report and fix each missing type. + foreach ( $missingTypes as $paramName => $info ) { + $missingList = array_unique( $info['missing'] ); + $newType = $info['existing'] . '|' . implode( '|', $missingList ); + + $fix = $phpcsFile->addFixableError( + '@param %s is missing type(s) %s based on is_* checks in function body', + $info['token'], + 'MissingParamType', + array( $paramName, implode( ', ', $missingList ) ) + ); + + if ( $fix ) { + $this->fixParamType( $phpcsFile, $info['token'], $info['existing'], $newType ); + } + } + } + + /** + * Find the docblock for a function. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The function token position. + * + * @return false|int The docblock opener position, or false. + */ + private function findDocblock( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + $ignore = array( + T_WHITESPACE, + T_STATIC, + T_PUBLIC, + T_PRIVATE, + T_PROTECTED, + T_ABSTRACT, + T_FINAL, + ); + + $prev = $phpcsFile->findPrevious( $ignore, $stackPtr - 1, null, true ); + + if ( false !== $prev && $tokens[ $prev ]['code'] === T_DOC_COMMENT_CLOSE_TAG ) { + return $tokens[ $prev ]['comment_opener']; + } + + return false; + } + + /** + * Get function parameters. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The function token position. + * + * @return array Array of parameter info. + */ + private function getParameters( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + $params = array(); + + if ( ! isset( $tokens[ $stackPtr ]['parenthesis_opener'] ) ) { + return $params; + } + + $opener = $tokens[ $stackPtr ]['parenthesis_opener']; + $closer = $tokens[ $stackPtr ]['parenthesis_closer']; + + for ( $i = $opener + 1; $i < $closer; $i++ ) { + if ( $tokens[ $i ]['code'] === T_VARIABLE ) { + $params[] = array( + 'name' => $tokens[ $i ]['content'], + 'token' => $i, + ); + } + } + + return $params; + } + + /** + * Get existing @param tags from docblock with their types. + * + * @param File $phpcsFile The file being scanned. + * @param int $docblock The docblock opener position. + * + * @return array Associative array of param name => array('type' => string, 'token' => int). + */ + private function getExistingParamTags( File $phpcsFile, $docblock ) { + $tokens = $phpcsFile->getTokens(); + $existing = array(); + + if ( ! isset( $tokens[ $docblock ]['comment_closer'] ) ) { + return $existing; + } + + $closer = $tokens[ $docblock ]['comment_closer']; + + for ( $i = $docblock; $i < $closer; $i++ ) { + if ( $tokens[ $i ]['code'] !== T_DOC_COMMENT_TAG ) { + continue; + } + + if ( $tokens[ $i ]['content'] !== '@param' ) { + continue; + } + + // Find the type and parameter name in this @param line. + for ( $j = $i + 1; $j < $closer; $j++ ) { + if ( $tokens[ $j ]['code'] === T_DOC_COMMENT_STRING ) { + $content = $tokens[ $j ]['content']; + + // Parse "type $varName description" format. + if ( preg_match( '/^([^\s]+)\s+(\$\w+)/', $content, $matches ) ) { + $existing[ $matches[2] ] = array( + 'type' => $matches[1], + 'token' => $j, + ); + } + break; + } + + if ( $tokens[ $j ]['code'] === T_DOC_COMMENT_TAG ) { + break; + } + } + } + + return $existing; + } + + /** + * Find is_* checks for a specific parameter in the function body. + * + * @param File $phpcsFile The file being scanned. + * @param string $varName The variable name. + * @param int $scopeOpener The function scope opener. + * @param int $scopeCloser The function scope closer. + * + * @return array Array of types that are checked. + */ + private function findIsChecksForParam( File $phpcsFile, $varName, $scopeOpener, $scopeCloser ) { + $tokens = $phpcsFile->getTokens(); + $checkedTypes = array(); + + for ( $i = $scopeOpener + 1; $i < $scopeCloser; $i++ ) { + if ( $tokens[ $i ]['code'] !== T_STRING ) { + continue; + } + + $functionName = $tokens[ $i ]['content']; + + if ( ! isset( $this->isCheckToType[ $functionName ] ) ) { + continue; + } + + // Check if followed by ( $varName ). + $openParen = $phpcsFile->findNext( T_WHITESPACE, $i + 1, $scopeCloser, true ); + + if ( false === $openParen || $tokens[ $openParen ]['code'] !== T_OPEN_PARENTHESIS ) { + continue; + } + + $varToken = $phpcsFile->findNext( T_WHITESPACE, $openParen + 1, $scopeCloser, true ); + + if ( false === $varToken || $tokens[ $varToken ]['code'] !== T_VARIABLE || $tokens[ $varToken ]['content'] !== $varName ) { + continue; + } + + // Make sure the variable is used directly, not as array access or object property. + $afterVar = $phpcsFile->findNext( T_WHITESPACE, $varToken + 1, $scopeCloser, true ); + + if ( false !== $afterVar ) { + $afterCode = $tokens[ $afterVar ]['code']; + + // Skip $var[...] (array access) or $var->... (object property). + if ( $afterCode === T_OPEN_SQUARE_BRACKET || $afterCode === T_OBJECT_OPERATOR || $afterCode === T_NULLSAFE_OBJECT_OPERATOR ) { + continue; + } + } + + $checkedTypes[] = $this->isCheckToType[ $functionName ]; + } + + return array_unique( $checkedTypes ); + } + + /** + * Check if an existing type string includes a specific type. + * + * @param string $existingType The existing type string (e.g., "string|array"). + * @param string $checkType The type to check for. + * + * @return bool + */ + private function typeIncludesType( $existingType, $checkType ) { + // Handle compound types from is_numeric. + $checkTypes = explode( '|', $checkType ); + + // Split existing type by | for checking. + $existingTypes = explode( '|', $existingType ); + + // If callable is in existing types, treat array and string as covered (callable can be an array or string). + if ( $checkType === 'array' || $checkType === 'string' ) { + foreach ( $existingTypes as $existing ) { + $normalizedExisting = $this->normalizeType( $existing ); + + if ( $normalizedExisting === 'callable' ) { + return true; + } + } + } + + foreach ( $checkTypes as $singleType ) { + foreach ( $existingTypes as $existing ) { + // Normalize types for comparison. + $normalizedExisting = $this->normalizeType( $existing ); + $normalizedCheck = $this->normalizeType( $singleType ); + + if ( $normalizedExisting === $normalizedCheck ) { + return true; + } + + // Check if 'mixed' covers everything. + if ( $normalizedExisting === 'mixed' ) { + return true; + } + } + } + + return false; + } + + /** + * Normalize a type for comparison. + * + * @param string $type The type to normalize. + * + * @return string + */ + private function normalizeType( $type ) { + $type = strtolower( trim( $type ) ); + + // Normalize common aliases. + $aliases = array( + 'integer' => 'int', + 'boolean' => 'bool', + 'double' => 'float', + 'real' => 'float', + 'long' => 'int', + 'stdclass' => 'object', + 'closure' => 'callable', + ); + + if ( isset( $aliases[ $type ] ) ) { + return $aliases[ $type ]; + } + + return $type; + } + + /** + * Fix the @param type by replacing the old type with the new type. + * + * @param File $phpcsFile The file being scanned. + * @param int $tokenPtr The token position of the docblock string. + * @param string $oldType The old type string. + * @param string $newType The new type string. + * + * @return void + */ + private function fixParamType( File $phpcsFile, $tokenPtr, $oldType, $newType ) { + $tokens = $phpcsFile->getTokens(); + $content = $tokens[ $tokenPtr ]['content']; + + // Replace the old type with the new type. + $newContent = preg_replace( + '/^' . preg_quote( $oldType, '/' ) . '/', + $newType, + $content, + 1 + ); + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken( $tokenPtr, $newContent ); + $phpcsFile->fixer->endChangeset(); + } +} diff --git a/phpcs-sniffs/Formidable/ruleset.xml b/phpcs-sniffs/Formidable/ruleset.xml index 4c7ede4a6f..504157f4c6 100644 --- a/phpcs-sniffs/Formidable/ruleset.xml +++ b/phpcs-sniffs/Formidable/ruleset.xml @@ -71,4 +71,5 @@ + From ef6b6a9797135f9c655fcfb16fea3461f05d367f Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Fri, 23 Jan 2026 11:05:40 -0400 Subject: [PATCH 2/6] Add some more exceptions and revert some changes --- classes/controllers/FrmXMLController.php | 4 +- classes/helpers/FrmXMLHelper.php | 2 +- classes/models/FrmEmail.php | 2 +- .../Commenting/AddMissingParamTypeSniff.php | 99 +++++++++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) diff --git a/classes/controllers/FrmXMLController.php b/classes/controllers/FrmXMLController.php index b15ad93dbf..3208c81d9b 100644 --- a/classes/controllers/FrmXMLController.php +++ b/classes/controllers/FrmXMLController.php @@ -443,8 +443,8 @@ public static function export_xml() { } /** - * @param array|string|array $type - * @param array $args + * @param array|string $type + * @param array $args * * @psalm-param array{ids?: mixed} $args * diff --git a/classes/helpers/FrmXMLHelper.php b/classes/helpers/FrmXMLHelper.php index 908195c2e1..9dbdcdd9ad 100644 --- a/classes/helpers/FrmXMLHelper.php +++ b/classes/helpers/FrmXMLHelper.php @@ -1889,7 +1889,7 @@ private static function remove_default_html( $html_name, $defaults, &$options ) } /** - * @param string|array $str + * @param string $str * * @return string */ diff --git a/classes/models/FrmEmail.php b/classes/models/FrmEmail.php index d52daf4b7c..854869e0a8 100644 --- a/classes/models/FrmEmail.php +++ b/classes/models/FrmEmail.php @@ -656,7 +656,7 @@ private function prepare_email_setting( $value, $user_id_args ) { * * @since 2.03.04 * - * @param string|array $emails + * @param string $emails * * @return array|string Emails. */ diff --git a/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php index cdf3073663..6c2145bdca 100644 --- a/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php +++ b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php @@ -265,6 +265,9 @@ private function findIsChecksForParam( File $phpcsFile, $varName, $scopeOpener, $tokens = $phpcsFile->getTokens(); $checkedTypes = array(); + // Find the first reassignment of this variable (if any). + $reassignmentPos = $this->findFirstReassignment( $phpcsFile, $varName, $scopeOpener, $scopeCloser ); + for ( $i = $scopeOpener + 1; $i < $scopeCloser; $i++ ) { if ( $tokens[ $i ]['code'] !== T_STRING ) { continue; @@ -301,12 +304,103 @@ private function findIsChecksForParam( File $phpcsFile, $varName, $scopeOpener, } } + // Skip if this is_* check occurs after the variable has been reassigned. + if ( false !== $reassignmentPos && $i > $reassignmentPos ) { + continue; + } + $checkedTypes[] = $this->isCheckToType[ $functionName ]; } return array_unique( $checkedTypes ); } + /** + * Find the first reassignment of a variable in the function body. + * + * @param File $phpcsFile The file being scanned. + * @param string $varName The variable name. + * @param int $scopeOpener The function scope opener. + * @param int $scopeCloser The function scope closer. + * + * @return false|int The position of the first reassignment, or false if none. + */ + private function findFirstReassignment( File $phpcsFile, $varName, $scopeOpener, $scopeCloser ) { + $tokens = $phpcsFile->getTokens(); + $reassignmentPos = false; + + for ( $i = $scopeOpener + 1; $i < $scopeCloser; $i++ ) { + if ( $tokens[ $i ]['code'] !== T_VARIABLE ) { + continue; + } + + if ( $tokens[ $i ]['content'] !== $varName ) { + continue; + } + + // Check if this is an assignment ($var = ...). + $next = $phpcsFile->findNext( T_WHITESPACE, $i + 1, $scopeCloser, true ); + + if ( false !== $next && $tokens[ $next ]['code'] === T_EQUAL ) { + $reassignmentPos = $i; + break; + } + } + + // Also check for FrmAppHelper::unserialize_or_decode( $var ) which changes the type. + $typeChangingCall = $this->findTypeChangingCall( $phpcsFile, $varName, $scopeOpener, $scopeCloser ); + + // Return the earliest position. + if ( false === $reassignmentPos ) { + return $typeChangingCall; + } + + if ( false === $typeChangingCall ) { + return $reassignmentPos; + } + + return min( $reassignmentPos, $typeChangingCall ); + } + + /** + * Find calls that change a variable's type (like FrmAppHelper::unserialize_or_decode). + * + * @param File $phpcsFile The file being scanned. + * @param string $varName The variable name. + * @param int $scopeOpener The function scope opener. + * @param int $scopeCloser The function scope closer. + * + * @return false|int The position of the call, or false if none. + */ + private function findTypeChangingCall( File $phpcsFile, $varName, $scopeOpener, $scopeCloser ) { + $tokens = $phpcsFile->getTokens(); + + for ( $i = $scopeOpener + 1; $i < $scopeCloser; $i++ ) { + if ( $tokens[ $i ]['code'] !== T_STRING ) { + continue; + } + + if ( $tokens[ $i ]['content'] !== 'unserialize_or_decode' ) { + continue; + } + + // Check if followed by ( $varName ). + $openParen = $phpcsFile->findNext( T_WHITESPACE, $i + 1, $scopeCloser, true ); + + if ( false === $openParen || $tokens[ $openParen ]['code'] !== T_OPEN_PARENTHESIS ) { + continue; + } + + $varToken = $phpcsFile->findNext( T_WHITESPACE, $openParen + 1, $scopeCloser, true ); + + if ( false !== $varToken && $tokens[ $varToken ]['code'] === T_VARIABLE && $tokens[ $varToken ]['content'] === $varName ) { + return $i; + } + } + + return false; + } + /** * Check if an existing type string includes a specific type. * @@ -378,6 +472,11 @@ private function normalizeType( $type ) { return $aliases[ $type ]; } + // Normalize typed arrays (array, array, etc.) to just 'array'. + if ( preg_match( '/^array\s* Date: Fri, 23 Jan 2026 11:06:45 -0400 Subject: [PATCH 3/6] Revert another change --- classes/models/FrmDb.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/models/FrmDb.php b/classes/models/FrmDb.php index 7e7968a7b4..876dbe2a7d 100644 --- a/classes/models/FrmDb.php +++ b/classes/models/FrmDb.php @@ -205,9 +205,9 @@ private static function interpret_array_to_sql( $key, $value, &$where, &$values * * @since 2.02.05 * - * @param string $key - * @param int|string|float $value - * @param string $where + * @param string $key + * @param int|string $value + * @param string $where * * @return void */ From 447e1c15641a600f7dd8801c98d38ae1dfb3ce4e Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Fri, 23 Jan 2026 11:07:28 -0400 Subject: [PATCH 4/6] Revert another change --- classes/helpers/FrmXMLHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/helpers/FrmXMLHelper.php b/classes/helpers/FrmXMLHelper.php index 9dbdcdd9ad..b3b6922cc2 100644 --- a/classes/helpers/FrmXMLHelper.php +++ b/classes/helpers/FrmXMLHelper.php @@ -329,7 +329,7 @@ private static function fill_form( $item ) { } /** - * @param array|object $form + * @param array $form * * @return false|object */ From 07e70d3b3788290908eb5c00396f156cdc0de794 Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Fri, 23 Jan 2026 11:08:04 -0400 Subject: [PATCH 5/6] Run php cs fixer --- classes/controllers/FrmFormActionsController.php | 2 +- classes/models/FrmForm.php | 4 ++-- classes/models/FrmSettings.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/classes/controllers/FrmFormActionsController.php b/classes/controllers/FrmFormActionsController.php index f24f52b5b5..5db7552e0f 100644 --- a/classes/controllers/FrmFormActionsController.php +++ b/classes/controllers/FrmFormActionsController.php @@ -639,7 +639,7 @@ public static function trigger_create_actions( $entry_id, $form_id, $args = arra /** * @param string $event * @param int|object|string $form - * @param int|string|object $entry + * @param int|object|string $entry * @param string $type * @param array $args * diff --git a/classes/models/FrmForm.php b/classes/models/FrmForm.php index 47d4eb09f3..d77620a6aa 100644 --- a/classes/models/FrmForm.php +++ b/classes/models/FrmForm.php @@ -605,7 +605,7 @@ public static function translatable_strings( $form ) { } /** - * @param int|array $id + * @param array|int $id * @param string $status * * @return bool|int @@ -837,7 +837,7 @@ public static function get_key_by_id( $id ) { * * @since 2.0.9 * - * @param int|object|array $form + * @param array|int|object $form * * @return void */ diff --git a/classes/models/FrmSettings.php b/classes/models/FrmSettings.php index 8f13189e21..ad87e1449a 100644 --- a/classes/models/FrmSettings.php +++ b/classes/models/FrmSettings.php @@ -635,7 +635,7 @@ private function update_roles( $params ) { * * @param string $key The setting key to update. * @param mixed $value The new value for the setting. - * @param string|callable $sanitize The name of the sanitization function to apply to the new value. + * @param callable|string $sanitize The name of the sanitization function to apply to the new value. * * @return bool True on success, false on failure. */ From 128662f74c1e192aecad0d6c367c502621d7a35d Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Fri, 23 Jan 2026 11:09:38 -0400 Subject: [PATCH 6/6] Revert another change and ignore callable --- classes/models/FrmSettings.php | 6 +++--- .../Sniffs/Commenting/AddMissingParamTypeSniff.php | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/classes/models/FrmSettings.php b/classes/models/FrmSettings.php index ad87e1449a..0e8ac0a720 100644 --- a/classes/models/FrmSettings.php +++ b/classes/models/FrmSettings.php @@ -633,9 +633,9 @@ private function update_roles( $params ) { * * @since 6.9 * - * @param string $key The setting key to update. - * @param mixed $value The new value for the setting. - * @param callable|string $sanitize The name of the sanitization function to apply to the new value. + * @param string $key The setting key to update. + * @param mixed $value The new value for the setting. + * @param string $sanitize The name of the sanitization function to apply to the new value. * * @return bool True on success, false on failure. */ diff --git a/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php index 6c2145bdca..f465cde47f 100644 --- a/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php +++ b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingParamTypeSniff.php @@ -36,7 +36,6 @@ class AddMissingParamTypeSniff implements Sniff { 'is_bool' => 'bool', 'is_null' => 'null', 'is_numeric' => 'string', - 'is_callable' => 'callable', 'is_iterable' => 'iterable', 'is_resource' => 'resource', );