From 4f8741c75eb44f4cf4131bdc91bb0fc28a0cfe54 Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Tue, 13 Jan 2026 16:59:58 -0400 Subject: [PATCH] Add sniffs to catch redundant isset checks --- .../RedundantIssetBeforeNotEmptySniff.php | 155 ++++++++++++++++++ .../RedundantNotIssetBeforeEmptySniff.php | 155 ++++++++++++++++++ phpcs.xml | 12 +- 3 files changed, 317 insertions(+), 5 deletions(-) create mode 100644 phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantIssetBeforeNotEmptySniff.php create mode 100644 phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantNotIssetBeforeEmptySniff.php diff --git a/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantIssetBeforeNotEmptySniff.php b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantIssetBeforeNotEmptySniff.php new file mode 100644 index 0000000000..6b2421f0a6 --- /dev/null +++ b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantIssetBeforeNotEmptySniff.php @@ -0,0 +1,155 @@ +getTokens(); + + // Get the opening parenthesis of isset(). + $issetOpen = $phpcsFile->findNext( T_WHITESPACE, $stackPtr + 1, null, true ); + + if ( false === $issetOpen || $tokens[ $issetOpen ]['code'] !== T_OPEN_PARENTHESIS ) { + return; + } + + // Get the closing parenthesis of isset(). + if ( ! isset( $tokens[ $issetOpen ]['parenthesis_closer'] ) ) { + return; + } + + $issetClose = $tokens[ $issetOpen ]['parenthesis_closer']; + + // Get the variable expression inside isset(). + $issetVarContent = $this->getParenthesesContent( $phpcsFile, $issetOpen, $issetClose ); + + // Find the && operator after isset(). + $andOperator = $phpcsFile->findNext( T_WHITESPACE, $issetClose + 1, null, true ); + + if ( false === $andOperator || $tokens[ $andOperator ]['code'] !== T_BOOLEAN_AND ) { + return; + } + + // Find the ! (boolean not) after &&. + $notOperator = $phpcsFile->findNext( T_WHITESPACE, $andOperator + 1, null, true ); + + if ( false === $notOperator || $tokens[ $notOperator ]['code'] !== T_BOOLEAN_NOT ) { + return; + } + + // Find empty() after the !. + $emptyToken = $phpcsFile->findNext( T_WHITESPACE, $notOperator + 1, null, true ); + + if ( false === $emptyToken || $tokens[ $emptyToken ]['code'] !== T_EMPTY ) { + return; + } + + // Get the opening parenthesis of empty(). + $emptyOpen = $phpcsFile->findNext( T_WHITESPACE, $emptyToken + 1, null, true ); + + if ( false === $emptyOpen || $tokens[ $emptyOpen ]['code'] !== T_OPEN_PARENTHESIS ) { + return; + } + + // Get the closing parenthesis of empty(). + if ( ! isset( $tokens[ $emptyOpen ]['parenthesis_closer'] ) ) { + return; + } + + $emptyClose = $tokens[ $emptyOpen ]['parenthesis_closer']; + + // Get the variable expression inside empty(). + $emptyVarContent = $this->getParenthesesContent( $phpcsFile, $emptyOpen, $emptyClose ); + + // Check if the variables match. + if ( $issetVarContent !== $emptyVarContent ) { + return; + } + + $fix = $phpcsFile->addFixableError( + 'Redundant isset() before ! empty(). The empty() function already checks if a variable is set. Use "! empty( %s )" instead.', + $stackPtr, + 'Found', + array( $issetVarContent ) + ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + + // Remove isset( $var ) &&. + // Find the end of the && operator (including trailing whitespace). + $removeEnd = $phpcsFile->findNext( T_WHITESPACE, $andOperator + 1, null, true ); + + // Remove all tokens from isset to just before the !. + for ( $i = $stackPtr; $i < $removeEnd; $i++ ) { + $phpcsFile->fixer->replaceToken( $i, '' ); + } + + $phpcsFile->fixer->endChangeset(); + } + } + + /** + * Get the content inside parentheses (excluding the parentheses themselves). + * + * @param File $phpcsFile The file being scanned. + * @param int $openParen The position of the opening parenthesis. + * @param int $closeParen The position of the closing parenthesis. + * + * @return string + */ + private function getParenthesesContent( File $phpcsFile, $openParen, $closeParen ) { + $tokens = $phpcsFile->getTokens(); + $content = ''; + + for ( $i = $openParen + 1; $i < $closeParen; $i++ ) { + // Skip whitespace for comparison purposes. + if ( $tokens[ $i ]['code'] !== T_WHITESPACE ) { + $content .= $tokens[ $i ]['content']; + } + } + + return $content; + } +} diff --git a/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantNotIssetBeforeEmptySniff.php b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantNotIssetBeforeEmptySniff.php new file mode 100644 index 0000000000..3feb583ca3 --- /dev/null +++ b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantNotIssetBeforeEmptySniff.php @@ -0,0 +1,155 @@ +getTokens(); + + // Find isset() after the !. + $issetToken = $phpcsFile->findNext( T_WHITESPACE, $stackPtr + 1, null, true ); + + if ( false === $issetToken || $tokens[ $issetToken ]['code'] !== T_ISSET ) { + return; + } + + // Get the opening parenthesis of isset(). + $issetOpen = $phpcsFile->findNext( T_WHITESPACE, $issetToken + 1, null, true ); + + if ( false === $issetOpen || $tokens[ $issetOpen ]['code'] !== T_OPEN_PARENTHESIS ) { + return; + } + + // Get the closing parenthesis of isset(). + if ( ! isset( $tokens[ $issetOpen ]['parenthesis_closer'] ) ) { + return; + } + + $issetClose = $tokens[ $issetOpen ]['parenthesis_closer']; + + // Get the variable expression inside isset(). + $issetVarContent = $this->getParenthesesContent( $phpcsFile, $issetOpen, $issetClose ); + + // Find the || operator after isset(). + $orOperator = $phpcsFile->findNext( T_WHITESPACE, $issetClose + 1, null, true ); + + if ( false === $orOperator || $tokens[ $orOperator ]['code'] !== T_BOOLEAN_OR ) { + return; + } + + // Find empty() after ||. + $emptyToken = $phpcsFile->findNext( T_WHITESPACE, $orOperator + 1, null, true ); + + if ( false === $emptyToken || $tokens[ $emptyToken ]['code'] !== T_EMPTY ) { + return; + } + + // Get the opening parenthesis of empty(). + $emptyOpen = $phpcsFile->findNext( T_WHITESPACE, $emptyToken + 1, null, true ); + + if ( false === $emptyOpen || $tokens[ $emptyOpen ]['code'] !== T_OPEN_PARENTHESIS ) { + return; + } + + // Get the closing parenthesis of empty(). + if ( ! isset( $tokens[ $emptyOpen ]['parenthesis_closer'] ) ) { + return; + } + + $emptyClose = $tokens[ $emptyOpen ]['parenthesis_closer']; + + // Get the variable expression inside empty(). + $emptyVarContent = $this->getParenthesesContent( $phpcsFile, $emptyOpen, $emptyClose ); + + // Check if the variables match. + if ( $issetVarContent !== $emptyVarContent ) { + return; + } + + $fix = $phpcsFile->addFixableError( + 'Redundant ! isset() before empty(). The empty() function already checks if a variable is set. Use "empty( %s )" instead.', + $stackPtr, + 'Found', + array( $issetVarContent ) + ); + + if ( true === $fix ) { + $phpcsFile->fixer->beginChangeset(); + + // Remove ! isset( $var ) ||. + // Find the start of empty() (the token after ||). + $removeEnd = $phpcsFile->findNext( T_WHITESPACE, $orOperator + 1, null, true ); + + // Remove all tokens from ! to just before empty(). + for ( $i = $stackPtr; $i < $removeEnd; $i++ ) { + $phpcsFile->fixer->replaceToken( $i, '' ); + } + + $phpcsFile->fixer->endChangeset(); + } + } + + /** + * Get the content inside parentheses (excluding the parentheses themselves). + * + * @param File $phpcsFile The file being scanned. + * @param int $openParen The position of the opening parenthesis. + * @param int $closeParen The position of the closing parenthesis. + * + * @return string + */ + private function getParenthesesContent( File $phpcsFile, $openParen, $closeParen ) { + $tokens = $phpcsFile->getTokens(); + $content = ''; + + for ( $i = $openParen + 1; $i < $closeParen; $i++ ) { + // Skip whitespace for comparison purposes. + if ( $tokens[ $i ]['code'] !== T_WHITESPACE ) { + $content .= $tokens[ $i ]['content']; + } + } + + return $content; + } +} diff --git a/phpcs.xml b/phpcs.xml index ea7e8302d6..f695b67445 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -239,18 +239,20 @@ - + + + - - - + + + - +