diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index f0a6bc5a55..8a46cdb28e 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -19,7 +19,6 @@ 'no_useless_else' => true, 'no_superfluous_elseif' => true, 'elseif' => true, - 'phpdoc_add_missing_param_annotation' => true, 'no_extra_blank_lines' => true, 'no_trailing_whitespace' => true, 'no_whitespace_in_blank_line' => true, diff --git a/classes/helpers/FrmAppHelper.php b/classes/helpers/FrmAppHelper.php index 4e39c26811..78851d3c0d 100644 --- a/classes/helpers/FrmAppHelper.php +++ b/classes/helpers/FrmAppHelper.php @@ -1167,6 +1167,8 @@ private static function allowed_html( $allowed ) { /** * @since 2.05.03 + * + * @return array */ private static function safe_html() { $allow_class = array( diff --git a/classes/helpers/FrmEntriesListHelper.php b/classes/helpers/FrmEntriesListHelper.php index a7121f6fc8..b157de81c1 100644 --- a/classes/helpers/FrmEntriesListHelper.php +++ b/classes/helpers/FrmEntriesListHelper.php @@ -17,6 +17,9 @@ class FrmEntriesListHelper extends FrmListHelper { */ public $total_items = 0; + /** + * @param array $args + */ public function __construct( $args ) { parent::__construct( $args ); $this->screen->set_screen_reader_content( diff --git a/classes/helpers/FrmFormsListHelper.php b/classes/helpers/FrmFormsListHelper.php index fdf5a85983..0277cfc59c 100644 --- a/classes/helpers/FrmFormsListHelper.php +++ b/classes/helpers/FrmFormsListHelper.php @@ -12,6 +12,9 @@ class FrmFormsListHelper extends FrmListHelper { public $total_items = 0; + /** + * @param array $args + */ public function __construct( $args ) { $this->status = self::get_param( array( 'param' => 'form_type' ) ); diff --git a/classes/helpers/FrmListHelper.php b/classes/helpers/FrmListHelper.php index d633ac5fe7..f70ae754c0 100644 --- a/classes/helpers/FrmListHelper.php +++ b/classes/helpers/FrmListHelper.php @@ -162,6 +162,9 @@ public function ajax_user_can() { return current_user_can( 'administrator' ); } + /** + * @return array + */ public function get_columns() { return array(); } diff --git a/classes/models/FrmMigrate.php b/classes/models/FrmMigrate.php index ccb064bafc..f122bf9823 100644 --- a/classes/models/FrmMigrate.php +++ b/classes/models/FrmMigrate.php @@ -390,6 +390,9 @@ private function migrate_data( $old_db_version ) { } } + /** + * @return bool + */ public function uninstall() { if ( ! current_user_can( 'administrator' ) ) { $frm_settings = FrmAppHelper::get_settings(); diff --git a/classes/models/FrmOnSubmitAction.php b/classes/models/FrmOnSubmitAction.php index a8f1d956e2..afba42dc46 100644 --- a/classes/models/FrmOnSubmitAction.php +++ b/classes/models/FrmOnSubmitAction.php @@ -50,6 +50,9 @@ public function form( $instance, $args = array() ) { include FrmAppHelper::plugin_path() . '/classes/views/frm-form-actions/on_submit_settings.php'; } + /** + * @return array + */ public function get_defaults() { return array( 'success_action' => FrmOnSubmitHelper::get_default_action_type(), diff --git a/classes/models/FrmSpamCheckUseWPComments.php b/classes/models/FrmSpamCheckUseWPComments.php index 420fee2d43..5d856c102c 100644 --- a/classes/models/FrmSpamCheckUseWPComments.php +++ b/classes/models/FrmSpamCheckUseWPComments.php @@ -13,6 +13,9 @@ class FrmSpamCheckUseWPComments extends FrmSpamCheck { + /** + * @return bool + */ protected function check() { $spam_comments = get_comments( array( diff --git a/classes/models/FrmSpamCheckWPDisallowedWords.php b/classes/models/FrmSpamCheckWPDisallowedWords.php index 7068ba17b2..82a0535283 100644 --- a/classes/models/FrmSpamCheckWPDisallowedWords.php +++ b/classes/models/FrmSpamCheckWPDisallowedWords.php @@ -13,6 +13,9 @@ class FrmSpamCheckWPDisallowedWords extends FrmSpamCheck { + /** + * @return bool + */ public function check() { $mod_keys = trim( $this->get_disallowed_words() ); diff --git a/classes/models/fields/FrmFieldNumber.php b/classes/models/fields/FrmFieldNumber.php index 0596b4d306..8a6711324e 100644 --- a/classes/models/fields/FrmFieldNumber.php +++ b/classes/models/fields/FrmFieldNumber.php @@ -59,6 +59,9 @@ protected function add_extra_html_atts( $args, &$input_html ) { $this->add_min_max( $args, $input_html ); } + /** + * @param array $args + */ public function validate( $args ) { $errors = array(); diff --git a/classes/models/fields/FrmFieldText.php b/classes/models/fields/FrmFieldText.php index 3449867868..bd9bb9bbe8 100644 --- a/classes/models/fields/FrmFieldText.php +++ b/classes/models/fields/FrmFieldText.php @@ -39,6 +39,9 @@ protected function field_settings_for_type() { ); } + /** + * @param array $args + */ public function validate( $args ) { $errors = parent::validate( $args ); $max_length = intval( FrmField::get_option( $this->field, 'max' ) ); diff --git a/classes/models/fields/FrmFieldUrl.php b/classes/models/fields/FrmFieldUrl.php index 0f947476cb..dc09eea21d 100644 --- a/classes/models/fields/FrmFieldUrl.php +++ b/classes/models/fields/FrmFieldUrl.php @@ -65,6 +65,9 @@ protected function fill_default_atts( &$atts ) { } } + /** + * @param array $args + */ public function validate( $args ) { $value = $args['value']; diff --git a/classes/models/fields/FrmFieldUserID.php b/classes/models/fields/FrmFieldUserID.php index 1ec0b2c0b8..e7a7f38fb5 100644 --- a/classes/models/fields/FrmFieldUserID.php +++ b/classes/models/fields/FrmFieldUserID.php @@ -48,6 +48,9 @@ protected function include_form_builder_file() { return FrmAppHelper::plugin_path() . '/classes/views/frm-fields/back-end/field-user-id.php'; } + /** + * @param array $args + */ public function prepare_field_html( $args ) { $args = $this->fill_display_field_values( $args ); $value = $this->get_field_value( $args ); @@ -71,6 +74,11 @@ protected function get_field_value( $args ) { return is_numeric( $this->field['value'] ) || $posted_value || $updating ? $this->field['value'] : $user_ID; } + /** + * @param array $args + * + * @return array + */ public function validate( $args ) { // phpcs:ignore Universal.Operators.StrictComparisons if ( '' == $args['value'] ) { diff --git a/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingDocblockSniff.php b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingDocblockSniff.php new file mode 100644 index 0000000000..d745997289 --- /dev/null +++ b/phpcs-sniffs/Formidable/Sniffs/Commenting/AddMissingDocblockSniff.php @@ -0,0 +1,771 @@ +getTokens(); + + // Skip if no scope (abstract method, interface method). + if ( ! isset( $tokens[ $stackPtr ]['scope_opener'] ) || ! isset( $tokens[ $stackPtr ]['scope_closer'] ) ) { + return; + } + + // Skip if function is inside a class that extends another class. + if ( $this->isInChildClass( $phpcsFile, $stackPtr ) ) { + return; + } + + // Check if function already has a docblock. + $existingDocblock = $this->findDocblock( $phpcsFile, $stackPtr ); + + if ( false !== $existingDocblock ) { + // Has docblock - check for missing @param or @return. + $this->processMissingAnnotations( $phpcsFile, $stackPtr, $existingDocblock ); + return; + } + + // No docblock - determine what we can add with certainty. + $params = $this->getParameters( $phpcsFile, $stackPtr ); + $returnType = $this->detectCertainReturnType( $phpcsFile, $stackPtr ); + + // Get certain param types. + $certainParams = $this->getCertainParamTypes( $phpcsFile, $stackPtr, $params ); + + // Only add docblock if we have something certain to add. + if ( empty( $certainParams ) && null === $returnType ) { + return; + } + + $fix = $phpcsFile->addFixableError( + 'Missing docblock for function.', + $stackPtr, + 'MissingDocblock' + ); + + if ( $fix ) { + $this->addDocblock( $phpcsFile, $stackPtr, $params, $certainParams, $returnType ); + } + } + + /** + * Process existing docblock for missing annotations. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The function token position. + * @param int $docblock The docblock opener position. + * + * @return void + */ + private function processMissingAnnotations( File $phpcsFile, $stackPtr, $docblock ) { + $tokens = $phpcsFile->getTokens(); + + $params = $this->getParameters( $phpcsFile, $stackPtr ); + $certainParams = $this->getCertainParamTypes( $phpcsFile, $stackPtr, $params ); + $existingParams = $this->getExistingParamTags( $phpcsFile, $docblock ); + $hasReturnTag = $this->hasReturnTag( $phpcsFile, $docblock ); + $returnType = $this->detectCertainReturnType( $phpcsFile, $stackPtr ); + + // Check for missing @param tags. + $missingParams = array(); + + foreach ( $certainParams as $paramName => $paramType ) { + if ( ! isset( $existingParams[ $paramName ] ) ) { + $missingParams[ $paramName ] = $paramType; + } + } + + // Check for missing @return tag. + $missingReturn = ( ! $hasReturnTag && null !== $returnType ); + + if ( empty( $missingParams ) && ! $missingReturn ) { + return; + } + + $message = 'Missing'; + + if ( ! empty( $missingParams ) ) { + $message .= ' @param for: ' . implode( ', ', array_keys( $missingParams ) ); + } + + if ( $missingReturn ) { + $message .= ( ! empty( $missingParams ) ? ' and' : '' ) . ' @return ' . $returnType; + } + + $fix = $phpcsFile->addFixableError( + $message, + $docblock, + 'MissingAnnotation' + ); + + if ( $fix ) { + $this->addMissingAnnotations( $phpcsFile, $docblock, $missingParams, $missingReturn ? $returnType : null ); + } + } + + /** + * 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 certain param types based on usage or naming. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The function token position. + * @param array $params The function parameters. + * + * @return array Associative array of param name => type. + */ + private function getCertainParamTypes( File $phpcsFile, $stackPtr, $params ) { + $tokens = $phpcsFile->getTokens(); + $certainTypes = array(); + + if ( empty( $params ) ) { + return $certainTypes; + } + + $scopeOpener = $tokens[ $stackPtr ]['scope_opener']; + $scopeCloser = $tokens[ $stackPtr ]['scope_closer']; + + foreach ( $params as $param ) { + $paramName = $param['name']; + + // Skip if param is checked with is_array() - type is uncertain. + if ( $this->hasIsArrayCheck( $phpcsFile, $paramName, $scopeOpener, $scopeCloser ) ) { + continue; + } + + // Check if param name is in our known array names. + if ( in_array( $paramName, $this->arrayParamNames, true ) ) { + $certainTypes[ $paramName ] = 'array'; + continue; + } + + // Check if param is used with [] syntax in function body. + if ( $this->isUsedAsArray( $phpcsFile, $paramName, $scopeOpener, $scopeCloser ) ) { + $certainTypes[ $paramName ] = 'array'; + } + } + + return $certainTypes; + } + + /** + * Check if a variable is checked with is_array(). + * + * @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 bool + */ + private function hasIsArrayCheck( 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'] !== 'is_array' ) { + 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 true; + } + } + + return false; + } + + /** + * Check if a variable is used as an array (with [] syntax). + * + * @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 bool + */ + private function isUsedAsArray( File $phpcsFile, $varName, $scopeOpener, $scopeCloser ) { + $tokens = $phpcsFile->getTokens(); + + for ( $i = $scopeOpener + 1; $i < $scopeCloser; $i++ ) { + if ( $tokens[ $i ]['code'] !== T_VARIABLE ) { + continue; + } + + if ( $tokens[ $i ]['content'] !== $varName ) { + continue; + } + + // Check if followed by [. + $next = $phpcsFile->findNext( T_WHITESPACE, $i + 1, $scopeCloser, true ); + + if ( false !== $next && $tokens[ $next ]['code'] === T_OPEN_SQUARE_BRACKET ) { + return true; + } + } + + return false; + } + + /** + * Detect certain return type from function body. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The function token position. + * + * @return string|null The certain return type, or null if uncertain. + */ + private function detectCertainReturnType( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + $scopeOpener = $tokens[ $stackPtr ]['scope_opener']; + $scopeCloser = $tokens[ $stackPtr ]['scope_closer']; + + $returnTypes = array(); + $hasUncertainType = false; + $current = $scopeOpener; + + while ( $current < $scopeCloser ) { + $return = $phpcsFile->findNext( T_RETURN, $current + 1, $scopeCloser ); + + if ( false === $return ) { + break; + } + + // Skip if inside nested closure/function. + if ( $this->isInsideNestedScope( $phpcsFile, $return, $stackPtr ) ) { + $current = $return; + continue; + } + + $type = $this->getReturnValueType( $phpcsFile, $return, $scopeCloser ); + + if ( null !== $type ) { + $returnTypes[] = $type; + } else { + $hasUncertainType = true; + } + + $current = $return; + } + + if ( empty( $returnTypes ) ) { + return null; + } + + // All returns must be the same certain type. + $uniqueTypes = array_unique( $returnTypes ); + + if ( count( $uniqueTypes ) === 1 ) { + $type = $uniqueTypes[0]; + + // Skip int if there's an uncertain return type (e.g., return $variable). + if ( 'int' === $type && $hasUncertainType ) { + return null; + } + + return $type; + } + + // Multiple types - check if they're compatible. + // For now, only return if all are the same. + return null; + } + + /** + * Get the type of a return value. + * + * @param File $phpcsFile The file being scanned. + * @param int $returnPtr The return token position. + * @param int $scopeCloser The function scope closer. + * + * @return string|null The type if certain, null otherwise. + */ + private function getReturnValueType( File $phpcsFile, $returnPtr, $scopeCloser ) { + $tokens = $phpcsFile->getTokens(); + + // Find the semicolon that ends this return statement. + $semicolon = $phpcsFile->findNext( T_SEMICOLON, $returnPtr + 1, $scopeCloser ); + + if ( false === $semicolon ) { + return null; + } + + // Check if this return contains a ternary. + $ternaryThen = $phpcsFile->findNext( T_INLINE_THEN, $returnPtr + 1, $semicolon ); + + if ( false !== $ternaryThen ) { + return $this->getTernaryReturnType( $phpcsFile, $ternaryThen, $semicolon ); + } + + $next = $phpcsFile->findNext( T_WHITESPACE, $returnPtr + 1, $semicolon, true ); + + if ( false === $next ) { + return null; + } + + return $this->getSimpleValueType( $phpcsFile, $next, $semicolon ); + } + + /** + * Get the type from a ternary expression if both branches return the same type. + * + * @param File $phpcsFile The file being scanned. + * @param int $ternaryThen The position of the ? token. + * @param int $semicolon The position of the semicolon. + * + * @return string|null The type if both branches match, null otherwise. + */ + private function getTernaryReturnType( File $phpcsFile, $ternaryThen, $semicolon ) { + $tokens = $phpcsFile->getTokens(); + + // Find the : that separates the two branches. + $ternaryElse = $phpcsFile->findNext( T_INLINE_ELSE, $ternaryThen + 1, $semicolon ); + + if ( false === $ternaryElse ) { + return null; + } + + // Get the "then" value (between ? and :). + $thenValue = $phpcsFile->findNext( T_WHITESPACE, $ternaryThen + 1, $ternaryElse, true ); + + if ( false === $thenValue ) { + return null; + } + + // Get the "else" value (between : and ;). + $elseValue = $phpcsFile->findNext( T_WHITESPACE, $ternaryElse + 1, $semicolon, true ); + + if ( false === $elseValue ) { + return null; + } + + $thenType = $this->getSimpleValueType( $phpcsFile, $thenValue, $ternaryElse ); + $elseType = $this->getSimpleValueType( $phpcsFile, $elseValue, $semicolon ); + + // Only return type if both branches have the same certain type. + if ( null !== $thenType && $thenType === $elseType ) { + return $thenType; + } + + return null; + } + + /** + * Get the type of a simple value token. + * + * @param File $phpcsFile The file being scanned. + * @param int $valuePtr The value token position. + * @param int $endPtr The end boundary. + * + * @return string|null The type if certain, null otherwise. + */ + private function getSimpleValueType( File $phpcsFile, $valuePtr, $endPtr ) { + $tokens = $phpcsFile->getTokens(); + $code = $tokens[ $valuePtr ]['code']; + + // Hardcoded string. + if ( $code === T_CONSTANT_ENCAPSED_STRING ) { + return 'string'; + } + + // Hardcoded array. + if ( $code === T_ARRAY || $code === T_OPEN_SHORT_ARRAY ) { + return 'array'; + } + + // Hardcoded boolean - only if it's the only thing being returned. + if ( $code === T_TRUE || $code === T_FALSE ) { + // Make sure the next non-whitespace is the end boundary. + $afterBool = $phpcsFile->findNext( T_WHITESPACE, $valuePtr + 1, $endPtr, true ); + + if ( false === $afterBool ) { + return 'bool'; + } + + // There's more after the bool (like a comparison), skip. + return null; + } + + // Hardcoded integer. + if ( $code === T_LNUMBER ) { + return 'int'; + } + + // Hardcoded float. + if ( $code === T_DNUMBER ) { + return 'float'; + } + + return null; + } + + /** + * Check if a function is inside a class that extends another class. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The function token position. + * + * @return bool + */ + private function isInChildClass( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // Find the class this function belongs to. + if ( ! isset( $tokens[ $stackPtr ]['conditions'] ) ) { + return false; + } + + foreach ( $tokens[ $stackPtr ]['conditions'] as $scopePtr => $scopeType ) { + if ( $scopeType !== T_CLASS ) { + continue; + } + + // Found the class - check if it extends another class. + $extendsToken = $phpcsFile->findNext( T_EXTENDS, $scopePtr, $tokens[ $scopePtr ]['scope_opener'] ); + + if ( false !== $extendsToken ) { + return true; + } + } + + return false; + } + + /** + * Check if a token is inside a nested closure or function. + * + * @param File $phpcsFile The file being scanned. + * @param int $tokenPtr The token to check. + * @param int $functionPtr The outer function token. + * + * @return bool + */ + private function isInsideNestedScope( File $phpcsFile, $tokenPtr, $functionPtr ) { + $tokens = $phpcsFile->getTokens(); + + if ( isset( $tokens[ $tokenPtr ]['conditions'] ) ) { + foreach ( $tokens[ $tokenPtr ]['conditions'] as $scopePtr => $scopeType ) { + if ( $scopePtr !== $functionPtr && in_array( $scopeType, array( T_CLOSURE, T_FUNCTION ), true ) ) { + return true; + } + } + } + + return false; + } + + /** + * Get existing @param tags from docblock. + * + * @param File $phpcsFile The file being scanned. + * @param int $docblock The docblock opener position. + * + * @return array Associative array of param name => true. + */ + 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 parameter name in this @param line. + for ( $j = $i + 1; $j < $closer; $j++ ) { + if ( $tokens[ $j ]['code'] === T_DOC_COMMENT_STRING ) { + // Extract variable name from the string. + if ( preg_match( '/(\$\w+)/', $tokens[ $j ]['content'], $matches ) ) { + $existing[ $matches[1] ] = true; + } + break; + } + + if ( $tokens[ $j ]['code'] === T_DOC_COMMENT_TAG ) { + break; + } + } + } + + return $existing; + } + + /** + * Check if docblock has @return tag. + * + * @param File $phpcsFile The file being scanned. + * @param int $docblock The docblock opener position. + * + * @return bool + */ + private function hasReturnTag( File $phpcsFile, $docblock ) { + $tokens = $phpcsFile->getTokens(); + + if ( ! isset( $tokens[ $docblock ]['comment_closer'] ) ) { + return false; + } + + $closer = $tokens[ $docblock ]['comment_closer']; + + for ( $i = $docblock; $i < $closer; $i++ ) { + if ( $tokens[ $i ]['code'] === T_DOC_COMMENT_TAG && $tokens[ $i ]['content'] === '@return' ) { + return true; + } + } + + return false; + } + + /** + * Add a new docblock to a function. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The function token position. + * @param array $params All function parameters. + * @param array $certainParams Certain param types. + * @param string|null $returnType The return type, or null. + * + * @return void + */ + private function addDocblock( File $phpcsFile, $stackPtr, $params, $certainParams, $returnType ) { + $tokens = $phpcsFile->getTokens(); + $fixer = $phpcsFile->fixer; + + // Find the line start. + $lineStart = $stackPtr; + + while ( $lineStart > 0 && $tokens[ $lineStart - 1 ]['line'] === $tokens[ $stackPtr ]['line'] ) { + $lineStart--; + } + + // Get indentation. + $indent = ''; + + if ( $tokens[ $lineStart ]['code'] === T_WHITESPACE ) { + $indent = $tokens[ $lineStart ]['content']; + } + + // Build docblock. + $docblock = $indent . "/**\n"; + + // Add @param tags for certain types only. + foreach ( $params as $param ) { + $paramName = $param['name']; + + if ( isset( $certainParams[ $paramName ] ) ) { + $docblock .= $indent . " * @param " . $certainParams[ $paramName ] . " " . $paramName . "\n"; + } + } + + // Add @return if certain. + if ( null !== $returnType ) { + if ( ! empty( $certainParams ) ) { + $docblock .= $indent . " *\n"; + } + $docblock .= $indent . " * @return " . $returnType . "\n"; + } + + $docblock .= $indent . " */\n"; + + $fixer->beginChangeset(); + $fixer->addContentBefore( $lineStart, $docblock ); + $fixer->endChangeset(); + } + + /** + * Add missing annotations to existing docblock. + * + * @param File $phpcsFile The file being scanned. + * @param int $docblock The docblock opener position. + * @param array $missingParams Missing param types. + * @param string|null $returnType Missing return type, or null. + * + * @return void + */ + private function addMissingAnnotations( File $phpcsFile, $docblock, $missingParams, $returnType ) { + $tokens = $phpcsFile->getTokens(); + $fixer = $phpcsFile->fixer; + + $closer = $tokens[ $docblock ]['comment_closer']; + + // Find the last line before closing. + $lastContentLine = $tokens[ $closer ]['line'] - 1; + + // Get indentation from docblock opener. + $indent = ''; + $lineStart = $docblock; + + while ( $lineStart > 0 && $tokens[ $lineStart - 1 ]['line'] === $tokens[ $docblock ]['line'] ) { + $lineStart--; + } + + if ( $tokens[ $lineStart ]['code'] === T_WHITESPACE ) { + $indent = $tokens[ $lineStart ]['content']; + } + + // Find position to insert (before the closing tag). + $insertBefore = $closer; + + // Look for existing @return or @param to insert after. + $lastTag = null; + + for ( $i = $docblock; $i < $closer; $i++ ) { + if ( $tokens[ $i ]['code'] === T_DOC_COMMENT_TAG ) { + $lastTag = $i; + } + } + + $fixer->beginChangeset(); + + $content = ''; + + // Add missing @param tags. + foreach ( $missingParams as $paramName => $paramType ) { + $content .= "\n" . $indent . " * @param " . $paramType . " " . $paramName; + } + + // Add missing @return. + if ( null !== $returnType ) { + if ( ! empty( $missingParams ) ) { + $content .= "\n" . $indent . " *"; + } + $content .= "\n" . $indent . " * @return " . $returnType; + } + + // Find the token just before the closing */ to insert after. + $insertAfter = $closer - 1; + + while ( $insertAfter > $docblock && $tokens[ $insertAfter ]['code'] === T_DOC_COMMENT_WHITESPACE ) { + $insertAfter--; + } + + $fixer->addContent( $insertAfter, $content ); + $fixer->endChangeset(); + } +} diff --git a/phpcs-sniffs/Formidable/ruleset.xml b/phpcs-sniffs/Formidable/ruleset.xml index 71f0a3d615..4c7ede4a6f 100644 --- a/phpcs-sniffs/Formidable/ruleset.xml +++ b/phpcs-sniffs/Formidable/ruleset.xml @@ -70,4 +70,5 @@ + diff --git a/stripe/helpers/FrmTransLiteListHelper.php b/stripe/helpers/FrmTransLiteListHelper.php index e6481a530c..9bcccf74ea 100755 --- a/stripe/helpers/FrmTransLiteListHelper.php +++ b/stripe/helpers/FrmTransLiteListHelper.php @@ -20,6 +20,9 @@ class FrmTransLiteListHelper extends FrmListHelper { */ private $valid_entry_ids = array(); + /** + * @param array $args + */ public function __construct( $args ) { $this->table = FrmAppHelper::get_simple_request( array( diff --git a/tests/phpunit/base/FrmUnitTest.php b/tests/phpunit/base/FrmUnitTest.php index 82d41e4699..dc1abbcf27 100644 --- a/tests/phpunit/base/FrmUnitTest.php +++ b/tests/phpunit/base/FrmUnitTest.php @@ -464,6 +464,9 @@ public function get_footer_output() { return ob_get_clean(); } + /** + * @return array + */ public static function install_data() { return array( __DIR__ . '/testdata.xml', @@ -473,6 +476,9 @@ public static function install_data() { ); } + /** + * @param array $type + */ public static function generate_xml( $type, $xml_args ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh // Code copied from FrmXMLController::generate_xml global $wpdb; @@ -638,6 +644,10 @@ protected function create_users() { $this->assertNotEmpty( $subscriber ); } + /** + * @param array $method + * @param array $args + */ protected function run_private_method( $method, $args = array() ) { $m = new ReflectionMethod( $method[0], $method[1] ); $m->setAccessible( true ); diff --git a/tests/phpunit/base/frm_factory.php b/tests/phpunit/base/frm_factory.php index da6417014e..1f0af07d05 100644 --- a/tests/phpunit/base/frm_factory.php +++ b/tests/phpunit/base/frm_factory.php @@ -37,6 +37,9 @@ public function __construct( $factory = null ) { $this->default_generation_definitions['protect_files_role'] = ''; } + /** + * @param array $args + */ public function create_object( $args ) { $form = FrmForm::create( $args ); $field_values = FrmFieldsHelper::setup_new_vars( 'text', $form ); @@ -75,6 +78,9 @@ public function __construct( $factory = null ) { ); } + /** + * @param array $args + */ public function create_object( $args ) { $field_values = FrmFieldsHelper::setup_new_vars( $args['type'], $args['form_id'] ); unset( $args['type'], $args['form_id'] ); @@ -173,6 +179,9 @@ public function __construct( $factory = null ) { ); } + /** + * @param array $args + */ public function create_object( $args ) { $default_values = array( 'form_id' => $args['form_id'], diff --git a/tests/phpunit/emails/test_FrmEmail.php b/tests/phpunit/emails/test_FrmEmail.php index ef34218983..e8abb64a89 100644 --- a/tests/phpunit/emails/test_FrmEmail.php +++ b/tests/phpunit/emails/test_FrmEmail.php @@ -448,6 +448,10 @@ protected function create_entry( $form ) { return $entry; } + /** + * @param array $expected + * @param array $mock_email + */ protected function check_recipients( $expected, $mock_email, $cc_status = 'yes_cc', $bcc_status = 'yes_bcc' ) { $this->assertSame( $expected['to'], $mock_email['to'], 'To does not match expected.' ); $this->assertSame( $expected['cc'], $mock_email['cc'], 'CC does not match expected.' ); @@ -462,17 +466,29 @@ protected function check_recipients( $expected, $mock_email, $cc_status = 'yes_c } } + /** + * @param array $expected + * @param array $mock_email + */ protected function check_senders( $expected, $mock_email ) { $this->assertStringContainsString( 'From: ' . $expected['from'], $mock_email['header'], 'From does not match expected.' ); $this->assertStringContainsString( 'Reply-To: ' . $expected['reply_to'], $mock_email['header'], 'Reply-to does not match expected.' ); } + /** + * @param array $expected + * @param array $mock_email + */ protected function check_subject( $expected, $mock_email ) { if ( isset( $mock_email['subject'] ) ) { $this->assertSame( $expected['subject'], $mock_email['subject'], 'Subject does not match expected.' ); } } + /** + * @param array $expected + * @param array $mock_email + */ protected function check_message_body( $expected, $mock_email ) { // Remove line breaks from body for comparison $expected['body'] = preg_replace( "/\r|\n/", '', $expected['body'] ); @@ -481,28 +497,52 @@ protected function check_message_body( $expected, $mock_email ) { $this->assertSame( $expected['body'], $mock_email['body'], 'Message body does not match expected.' ); } + /** + * @param array $expected + * @param array $mock_email + */ protected function check_content_type( $expected, $mock_email ) { $this->assertStringContainsString( $expected['content_type'], $mock_email['header'], 'Content type does not match expected.' ); } + /** + * @param array $mock_email + */ protected function check_no_cc_included( $mock_email ) { $this->assertStringNotContainsString( 'Cc:', $mock_email['header'], 'CC is included when it should not be.' ); } + /** + * @param array $mock_email + */ protected function check_no_bcc_included( $mock_email ) { $this->assertStringNotContainsString( 'Bcc:', $mock_email['header'], 'BCC is included when it should not be.' ); } + /** + * @param array $to_emails + * @param array $args + */ public function add_to_emails( $to_emails, $values, $form_id, $args ) { $to_emails[] = 'test3@mail.com'; $to_emails[] = '1231231234'; return $to_emails; } + /** + * @param array $args + * + * @return string + */ public function change_email_subject( $subject, $args ) { return 'New subject'; } + /** + * @param array $args + * + * @return bool + */ public function send_separate_emails( $is_single, $args ) { return true; } diff --git a/tests/phpunit/entries/test_FrmPersonalData.php b/tests/phpunit/entries/test_FrmPersonalData.php index 00591aa501..75900b37a5 100644 --- a/tests/phpunit/entries/test_FrmPersonalData.php +++ b/tests/phpunit/entries/test_FrmPersonalData.php @@ -39,6 +39,9 @@ private function get_and_compare_entries( $expected, $email ) { $this->assertSame( asort( $expected ), asort( $entries ) ); } + /** + * @param array $entry_data + */ private function create_entries_for_user( $entry_data, $email ) { $user = get_user_by( 'email', $email ); $this->assertNotEmpty( $user ); diff --git a/tests/phpunit/entries/test_FrmShowEntryShortcode.php b/tests/phpunit/entries/test_FrmShowEntryShortcode.php index 6a872d9512..42bd0e0159 100644 --- a/tests/phpunit/entries/test_FrmShowEntryShortcode.php +++ b/tests/phpunit/entries/test_FrmShowEntryShortcode.php @@ -717,6 +717,9 @@ protected function get_test_entry( $include_meta ) { return FrmEntry::getOne( $entry_id, $include_meta ); } + /** + * @return array + */ private function expected_free_meta() { return array( FrmField::get_id_by_key( 'free-text-field' ) => 'Test Testerson', @@ -732,10 +735,16 @@ private function expected_free_meta() { ); } + /** + * @param array $atts + */ protected function get_formatted_content( $atts ) { return FrmEntriesController::show_entry_shortcode( $atts ); } + /** + * @param array $atts + */ protected function expected_html_content( $atts ) { $table = $this->table_header( $atts ); $table .= $this->two_cell_table_row( 'free-text-field', $atts ); @@ -754,6 +763,9 @@ protected function expected_html_content( $atts ) { return $table . $this->table_footer(); } + /** + * @param array $atts + */ protected function expected_plain_text_content( $atts ) { $content = $this->label_and_value_plain_text_row( 'free-text-field', $atts ); $content .= $this->label_and_value_plain_text_row( 'free-paragraph-field', $atts ); @@ -770,6 +782,11 @@ protected function expected_plain_text_content( $atts ) { return $content . $this->user_info_plain_text_rows( $atts ); } + /** + * @param array $atts + * + * @return string + */ protected function table_header( $atts ) { if ( ! empty( $atts['plain_text'] ) ) { return ''; @@ -798,10 +815,18 @@ protected function get_defaults() { return $defaults; } + /** + * @return string + */ protected function table_footer() { return ''; } + /** + * @param array $atts + * + * @return string + */ protected function two_cell_table_row( $field_key, $atts ) { $field = FrmField::getOne( $field_key ); $field_value = $this->get_field_html_value( $atts['entry'], $field, $atts ); @@ -813,6 +838,9 @@ protected function two_cell_table_row( $field_key, $atts ) { return $this->two_cell_table_row_for_value( $field->name, $field_value, $atts ); } + /** + * @param array $atts + */ protected function two_cell_table_row_for_value( $label, $field_value, $atts ) { $html = 'tr_style; @@ -835,6 +863,11 @@ protected function two_cell_table_row_for_value( $label, $field_value, $atts ) { return $html . '' . "\r\n"; } + /** + * @param array $atts + * + * @return string + */ protected function one_cell_table_row( $field_key, $atts ) { $field = FrmField::getOne( $field_key ); $field_value = $this->get_field_html_value( $atts['entry'], $field, $atts ); @@ -849,6 +882,11 @@ protected function one_cell_table_row( $field_key, $atts ) { return $html . '' . "\r\n"; } + /** + * @param array $atts + * + * @return string + */ protected function label_and_value_plain_text_row( $field_key, $atts ) { $field = FrmField::getOne( $field_key ); $field_value = $this->get_field_plain_text_value( $atts['entry'], $field, $atts ); @@ -866,6 +904,11 @@ protected function label_and_value_plain_text_row( $field_key, $atts ) { return $content . "\r\n"; } + /** + * @param array $atts + * + * @return string + */ protected function single_value_plain_text_row( $field_key, $atts ) { $field = FrmField::getOne( $field_key ); $field_value = $this->get_field_plain_text_value( $atts['entry'], $field, $atts ); @@ -910,6 +953,9 @@ protected function is_self_or_parent_in_array( $field_key, $array ) { return array_key_exists( $field_key, $array ); } + /** + * @param array $atts + */ protected function user_info_rows( $atts ) { // phpcs:ignore Universal.Operators.StrictComparisons if ( ! empty( $atts['user_info'] ) ) { @@ -923,6 +969,9 @@ protected function user_info_rows( $atts ) { return $html; } + /** + * @param array $atts + */ protected function user_info_plain_text_rows( $atts ) { // phpcs:ignore Universal.Operators.StrictComparisons if ( ! empty( $atts['user_info'] ) ) { @@ -936,11 +985,17 @@ protected function user_info_plain_text_rows( $atts ) { return $content; } + /** + * @param array $atts + */ protected function get_field_html_value( $entry, $field, $atts ) { $field_value = $this->get_field_value( $entry, $field, $atts ); return str_replace( array( "\r\n", "\n" ), '
', $field_value ); } + /** + * @param array $atts + */ protected function get_field_plain_text_value( $entry, $field, $atts ) { return $this->get_field_value( $entry, $field, $atts ); } @@ -1002,12 +1057,18 @@ protected function convert_field_array( $type, &$include_fields ) { } } + /** + * @param array $array + */ protected function convert_field_keys_to_ids( &$array ) { foreach ( $array as $key => $field_key ) { $array[ $key ] = FrmField::get_id_by_key( $field_key ); } } + /** + * @param array $array + */ protected function convert_field_keys_to_objects( &$array ) { foreach ( $array as $key => $field_key ) { $array[ $key ] = FrmField::getOne( $field_key ); @@ -1074,22 +1135,37 @@ protected function get_expected_default_shortcodes( $type, $atts ) { return $content; } + /** + * @return string + */ protected function table_row_start_tags( $type, $field ) { return $type === 'html' ? 'td_style . '>' : ''; } + /** + * @return string + */ protected function cell_separator( $type ) { return $type === 'html' ? 'td_style . '>' : ': '; } + /** + * @return string + */ protected function table_row_end_tags( $type ) { return $type === 'html' ? '' : ''; } + /** + * @return string + */ protected function after_table_row_tags( $type ) { return $type === 'html' ? "\r\n" : ''; } + /** + * @param array $atts + */ protected function expected_default_array( $atts ) { $fields = FrmField::get_all_for_form( $atts['form_id'], '', 'include' ); $expected = array(); @@ -1109,6 +1185,9 @@ protected function expected_default_array( $atts ) { return $expected; } + /** + * @param array $atts + */ protected function expected_array( $entry, $atts ) { $expected = array( 'free-text-field' => 'Test Testerson', @@ -1137,6 +1216,9 @@ protected function expected_array( $entry, $atts ) { return $expected; } + /** + * @param array $atts + */ protected function expected_json( $entry, $atts ) { $array = $this->expected_array( $entry, $atts ); return json_encode( $array ); @@ -1146,6 +1228,10 @@ protected function get_form_id_for_test() { return FrmForm::get_id_by_key( 'free_field_types' ); } + /** + * @param array $expected + * @param array $actual + */ protected function compare_array( $expected, $actual ) { if ( $expected !== $actual ) { foreach ( $expected as $k => $v ) { diff --git a/tests/phpunit/entries/test_FrmTableHTMLGenerator.php b/tests/phpunit/entries/test_FrmTableHTMLGenerator.php index 06e4754695..7bfa0d0568 100644 --- a/tests/phpunit/entries/test_FrmTableHTMLGenerator.php +++ b/tests/phpunit/entries/test_FrmTableHTMLGenerator.php @@ -60,6 +60,9 @@ public function test_remove_border() { $this->assertEquals( $table_generator->remove_border( $html ), $html ); } + /** + * @return array + */ private function _get_colors() { $atts = array( 'border_color' => 'ffffff', diff --git a/tests/phpunit/fields/test_FrmFieldValidate.php b/tests/phpunit/fields/test_FrmFieldValidate.php index 6e7b2e6133..d4d0f7fee8 100644 --- a/tests/phpunit/fields/test_FrmFieldValidate.php +++ b/tests/phpunit/fields/test_FrmFieldValidate.php @@ -80,6 +80,9 @@ public function test_format_validation() { } } + /** + * @return array + */ protected function expected_format_errors() { return array( array( diff --git a/tests/phpunit/misc/test_FrmAppHelper.php b/tests/phpunit/misc/test_FrmAppHelper.php index 2484a41225..0f6b39b21b 100644 --- a/tests/phpunit/misc/test_FrmAppHelper.php +++ b/tests/phpunit/misc/test_FrmAppHelper.php @@ -579,6 +579,9 @@ private function add_field_to_form( $form_id, $field_key ) { $this->factory->field->create( compact( 'type', 'form_id', 'field_key' ) ); } + /** + * @return string + */ public static function underscore_key_separator() { return '___'; } diff --git a/tests/phpunit/misc/test_FrmCurrencyHelper.php b/tests/phpunit/misc/test_FrmCurrencyHelper.php index 66c1aa9853..1fc9a10fc6 100644 --- a/tests/phpunit/misc/test_FrmCurrencyHelper.php +++ b/tests/phpunit/misc/test_FrmCurrencyHelper.php @@ -27,6 +27,9 @@ public function test_get_currency() { $this->assert_cad( $cad ); } + /** + * @param array $currency + */ private function assert_currency( $currency ) { $this->assertIsArray( $currency ); $this->assertIsString( $currency['name'] ); diff --git a/tests/phpunit/misc/test_FrmSpamCheckWPDisallowedWords.php b/tests/phpunit/misc/test_FrmSpamCheckWPDisallowedWords.php index ba1b757928..0b6a73bdf5 100644 --- a/tests/phpunit/misc/test_FrmSpamCheckWPDisallowedWords.php +++ b/tests/phpunit/misc/test_FrmSpamCheckWPDisallowedWords.php @@ -60,6 +60,8 @@ public function test_check() { /** * The name of the disallowed list of words was changed in WP 5.5. + * + * @return string */ private function get_disallowed_option_name() { $keys = get_option( 'disallowed_keys' ); diff --git a/tests/phpunit/views/shortcodes/test_FrmFieldShortcodes.php b/tests/phpunit/views/shortcodes/test_FrmFieldShortcodes.php index 58e2d6c26a..38360b6e09 100644 --- a/tests/phpunit/views/shortcodes/test_FrmFieldShortcodes.php +++ b/tests/phpunit/views/shortcodes/test_FrmFieldShortcodes.php @@ -54,6 +54,9 @@ protected function get_entry_for_test() { return FrmEntry::getOne( $entry_id, true ); } + /** + * @return array + */ protected function expected_free_meta() { return array( FrmField::get_id_by_key( 'free-text-field' ) => 'Test Testerson', @@ -69,6 +72,9 @@ protected function expected_free_meta() { ); } + /** + * @return array + */ protected function get_expected_field_values() { return array( 'free-text-field' => 'Test Testerson',