Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion classes/controllers/FrmXMLController.php
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ public static function generate_xml( $type, $args = array() ) { // phpcs:ignore
$args = wp_parse_args( $args, $defaults );

// Make sure ids are numeric.
if ( is_array( $args['ids'] ) && ! empty( $args['ids'] ) ) {
if ( is_array( $args['ids'] ) && $args['ids'] ) {
$args['ids'] = array_filter( $args['ids'], 'is_numeric' );
}

Expand Down
2 changes: 1 addition & 1 deletion classes/helpers/FrmFieldsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,7 @@ public static function get_other_val( $args ) { // phpcs:ignore SlevomatCodingSt
}

// For multi-select dropdowns only
if ( is_array( $field['value'] ) && ! empty( $field['value'] ) ) {
if ( is_array( $field['value'] ) && $field['value'] ) {
$other_val = reset( $field['value'] );
}
}//end if
Expand Down
2 changes: 1 addition & 1 deletion classes/helpers/FrmXMLHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,7 @@ private static function format_email_data( &$atts, $notification ) {

if ( 'custom' === $notification[ $f ] ) {
$atts[ $f ] = $notification[ 'cust_' . $f ];
} elseif ( is_numeric( $atts[ $f ] ) && ! empty( $atts[ $f ] ) ) {
} elseif ( is_numeric( $atts[ $f ] ) && $atts[ $f ] ) {
$atts[ $f ] = '[' . $atts[ $f ] . ']';
}
}
Expand Down
2 changes: 1 addition & 1 deletion classes/models/FrmForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ public static function get_key_by_id( $id ) {
* @return void
*/
public static function maybe_get_form( &$form ) {
if ( ! is_object( $form ) && ! is_array( $form ) && ! empty( $form ) ) {
if ( ! is_object( $form ) && ! is_array( $form ) && $form ) {
$form = self::getOne( $form );
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?php
/**
* Sniff to simplify redundant empty() checks after other checks on the same variable.
*
* @package Formidable\Sniffs\CodeAnalysis
*/

namespace Formidable\Sniffs\CodeAnalysis;

use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;

/**
* Detects patterns like check($x) && !empty($x) that can be simplified.
*
* Since the first check already confirms the variable is set, the empty() check
* is redundant for checking if it's set. We can simplify to just checking
* the truthiness of the variable.
*
* Bad:
* if ( is_array( $x ) && ! empty( $x ) ) {
* if ( is_string( $x ) && ! empty( $x ) ) {
* if ( isset( $x ) && ! empty( $x ) ) {
*
* Good:
* if ( is_array( $x ) && $x ) {
* if ( is_string( $x ) && $x ) {
* if ( isset( $x ) && $x ) {
*/
class RedundantEmptyAfterTypeCheckSniff implements Sniff {

/**
* Functions that confirm a variable is set.
*
* @var array
*/
private $checkFunctions = array(
'is_array',
'is_string',
'is_int',
'is_integer',
'is_float',
'is_double',
'is_bool',
'is_object',
'is_numeric',
'is_callable',
'is_resource',
'is_iterable',
'is_countable',
);

/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return array( T_STRING, T_ISSET );
}

/**
* 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();

// Check if this is a recognized check function or isset.
$funcName = $tokens[ $stackPtr ]['content'];

if ( $tokens[ $stackPtr ]['code'] === T_ISSET ) {
$funcName = 'isset';
} elseif ( ! in_array( $funcName, $this->checkFunctions, true ) ) {
return;
}
Comment on lines +73 to +80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "RedundantEmptyAfterTypeCheckSniff.php" -type f

Repository: Strategy11/formidable-forms

Length of output: 154


🏁 Script executed:

cat -n phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyAfterTypeCheckSniff.php

Repository: Strategy11/formidable-forms

Length of output: 7930


🏁 Script executed:

# Verify the token constants used in proposed fix exist in PHP_CodeSniffer
rg -t php "(T_OBJECT_OPERATOR|T_DOUBLE_COLON|T_FUNCTION)" phpcs-sniffs/ | head -20

Repository: Strategy11/formidable-forms

Length of output: 2863


🏁 Script executed:

# Check if there are any existing usages of findPrevious pattern in the codebase
rg -t php "findPrevious.*T_WHITESPACE.*true" phpcs-sniffs/ | head -10

Repository: Strategy11/formidable-forms

Length of output: 1715


Guard against method/static calls to avoid unsafe fixes.

The sniff currently treats any T_STRING matching a type check name (like is_array) as a built-in function without checking the preceding token. This causes it to incorrectly match method and static method calls such as Foo::is_array($x) or $obj->is_array($x), then auto-fix them to Foo::is_array($x) && $x or $obj->is_array($x) && $x, potentially breaking semantics if the method doesn't guarantee non-emptiness. Add a guard to skip when the previous non-whitespace token is T_DOUBLE_COLON or T_OBJECT_OPERATOR.

🛠️ Proposed fix
 		$funcName = $tokens[ $stackPtr ]['content'];
 
+		$prevNonWhitespace = $phpcsFile->findPrevious( T_WHITESPACE, $stackPtr - 1, null, true );
+		if ( false !== $prevNonWhitespace && in_array( $tokens[ $prevNonWhitespace ]['code'], array( T_OBJECT_OPERATOR, T_DOUBLE_COLON ), true ) ) {
+			return;
+		}
+
 		if ( $tokens[ $stackPtr ]['code'] === T_ISSET ) {
 			$funcName = 'isset';
 		} elseif ( ! in_array( $funcName, $this->checkFunctions, true ) ) {
 			return;
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check if this is a recognized check function or isset.
$funcName = $tokens[ $stackPtr ]['content'];
if ( $tokens[ $stackPtr ]['code'] === T_ISSET ) {
$funcName = 'isset';
} elseif ( ! in_array( $funcName, $this->checkFunctions, true ) ) {
return;
}
// Check if this is a recognized check function or isset.
$funcName = $tokens[ $stackPtr ]['content'];
$prevNonWhitespace = $phpcsFile->findPrevious( T_WHITESPACE, $stackPtr - 1, null, true );
if ( false !== $prevNonWhitespace && in_array( $tokens[ $prevNonWhitespace ]['code'], array( T_OBJECT_OPERATOR, T_DOUBLE_COLON ), true ) ) {
return;
}
if ( $tokens[ $stackPtr ]['code'] === T_ISSET ) {
$funcName = 'isset';
} elseif ( ! in_array( $funcName, $this->checkFunctions, true ) ) {
return;
}
🤖 Prompt for AI Agents
In
`@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyAfterTypeCheckSniff.php`
around lines 73 - 80, The sniff incorrectly treats any T_STRING matching a check
name as a bare function call; update the logic around $funcName/$stackPtr to
skip when the previous non-whitespace token before $stackPtr is a method/static
call operator by checking $tokens[ $stackPtr - 1 ] (or using findPrevious to get
the previous non-whitespace token) and returning early if that token's code is
T_DOUBLE_COLON or T_OBJECT_OPERATOR, so that Foo::is_array(...) and
$obj->is_array(...) are not treated as built-in checks and are not auto-fixed;
keep the existing isset handling and in_array($funcName, $this->checkFunctions,
true) check intact.


// Find the opening parenthesis.
$checkOpenParen = $phpcsFile->findNext( T_WHITESPACE, $stackPtr + 1, null, true );

if ( false === $checkOpenParen || $tokens[ $checkOpenParen ]['code'] !== T_OPEN_PARENTHESIS ) {
return;
}

if ( ! isset( $tokens[ $checkOpenParen ]['parenthesis_closer'] ) ) {
return;
}

$checkCloseParen = $tokens[ $checkOpenParen ]['parenthesis_closer'];

// Get the argument to the check function.
$checkArg = $this->getArgumentString( $phpcsFile, $checkOpenParen + 1, $checkCloseParen );

if ( empty( $checkArg ) ) {
return;
}

// Look for && after the check.
$nextToken = $phpcsFile->findNext( T_WHITESPACE, $checkCloseParen + 1, null, true );

// Check if we're inside a larger parenthesized expression.
if ( false !== $nextToken && $tokens[ $nextToken ]['code'] === T_CLOSE_PARENTHESIS ) {
$nextToken = $phpcsFile->findNext( T_WHITESPACE, $nextToken + 1, null, true );
}

if ( false === $nextToken || $tokens[ $nextToken ]['code'] !== T_BOOLEAN_AND ) {
return;
}

// Look for ! empty( ... ) after &&.
$afterAnd = $phpcsFile->findNext( T_WHITESPACE, $nextToken + 1, null, true );

if ( false === $afterAnd ) {
return;
}

// Check for negation.
if ( $tokens[ $afterAnd ]['code'] !== T_BOOLEAN_NOT ) {
return;
}

$emptyToken = $phpcsFile->findNext( T_WHITESPACE, $afterAnd + 1, null, true );

if ( false === $emptyToken || $tokens[ $emptyToken ]['code'] !== T_EMPTY ) {
return;
}

// Find the opening parenthesis of empty.
$emptyOpenParen = $phpcsFile->findNext( T_WHITESPACE, $emptyToken + 1, null, true );

if ( false === $emptyOpenParen || $tokens[ $emptyOpenParen ]['code'] !== T_OPEN_PARENTHESIS ) {
return;
}

if ( ! isset( $tokens[ $emptyOpenParen ]['parenthesis_closer'] ) ) {
return;
}

$emptyCloseParen = $tokens[ $emptyOpenParen ]['parenthesis_closer'];

// Get the argument to empty.
$emptyArg = $this->getArgumentString( $phpcsFile, $emptyOpenParen + 1, $emptyCloseParen );

if ( empty( $emptyArg ) ) {
return;
}

// Check if the arguments match.
if ( $checkArg !== $emptyArg ) {
return;
}

$fix = $phpcsFile->addFixableError(
'Redundant empty() after %s(). Use "%s( %s ) && %s" instead of "%s( %s ) && ! empty( %s )".',
$emptyToken,
'RedundantEmpty',
array( $funcName, $funcName, $checkArg, $checkArg, $funcName, $checkArg, $checkArg )
);

if ( true === $fix ) {
$phpcsFile->fixer->beginChangeset();

// Remove "! empty( " and keep just the argument content.
// Remove the "! " (negation and space after it).
$phpcsFile->fixer->replaceToken( $afterAnd, '' );

// Remove whitespace between ! and empty.
for ( $i = $afterAnd + 1; $i < $emptyToken; $i++ ) {
$phpcsFile->fixer->replaceToken( $i, '' );
}

// Remove "empty".
$phpcsFile->fixer->replaceToken( $emptyToken, '' );

// Remove whitespace and opening paren.
for ( $i = $emptyToken + 1; $i <= $emptyOpenParen; $i++ ) {
$phpcsFile->fixer->replaceToken( $i, '' );
}

// Remove the first whitespace inside the parenthesis (after open paren).
$firstArgToken = $phpcsFile->findNext( T_WHITESPACE, $emptyOpenParen + 1, $emptyCloseParen, true );

if ( false !== $firstArgToken ) {
for ( $i = $emptyOpenParen + 1; $i < $firstArgToken; $i++ ) {
$phpcsFile->fixer->replaceToken( $i, '' );
}
}

// Remove whitespace before closing paren and the closing paren itself.
$lastArgToken = $phpcsFile->findPrevious( T_WHITESPACE, $emptyCloseParen - 1, $emptyOpenParen, true );

if ( false !== $lastArgToken ) {
for ( $i = $lastArgToken + 1; $i <= $emptyCloseParen; $i++ ) {
$phpcsFile->fixer->replaceToken( $i, '' );
}
}

$phpcsFile->fixer->endChangeset();
}
}

/**
* Get the argument string from between parentheses.
*
* @param File $phpcsFile The file being scanned.
* @param int $start Start position (after open paren).
* @param int $end End position (close paren).
*
* @return string
*/
private function getArgumentString( File $phpcsFile, $start, $end ) {
$tokens = $phpcsFile->getTokens();
$arg = '';

for ( $i = $start; $i < $end; $i++ ) {
if ( $tokens[ $i ]['code'] === T_WHITESPACE ) {
continue;
}

$arg .= $tokens[ $i ]['content'];
}

return $arg;
}
}
1 change: 1 addition & 0 deletions phpcs-sniffs/Formidable/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<rule ref="Formidable.CodeAnalysis.FlipIfElseToEarlyReturn" />
<rule ref="Formidable.CodeAnalysis.FlipForeachIfToContinue" />
<rule ref="Formidable.CodeAnalysis.PreferArrayKeyFirstLast" />
<rule ref="Formidable.CodeAnalysis.RedundantEmptyAfterTypeCheck" />

<!-- PHPUnit -->
<rule ref="Formidable.PHPUnit.PreferAssertIsArray" />
Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/base/FrmUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ public static function generate_xml( $type, $xml_args ) { // phpcs:ignore Slevom
$args = wp_parse_args( $xml_args, $defaults );

// Make sure ids are numeric.
if ( is_array( $args['ids'] ) && ! empty( $args['ids'] ) ) {
if ( is_array( $args['ids'] ) && $args['ids'] ) {
$args['ids'] = array_filter( $args['ids'], 'is_numeric' );
}

Expand Down