From 976424afbb849320b1d9db9ff1bfe388d2bfcba2 Mon Sep 17 00:00:00 2001 From: Mike Letellier Date: Wed, 14 Jan 2026 17:05:16 -0400 Subject: [PATCH] Update sniffs to avoid false positive issues in pro --- .../RedundantEmptyOnAssignedVariableSniff.php | 107 +++++++++++++++++- .../SimplifyDualConditionToTernarySniff.php | 50 ++++++++ 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnAssignedVariableSniff.php b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnAssignedVariableSniff.php index 032b5bb614..ddafc6a1e3 100644 --- a/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnAssignedVariableSniff.php +++ b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnAssignedVariableSniff.php @@ -363,6 +363,9 @@ private function wasVariableUnconditionallyAssigned( File $phpcsFile, $functionT // The statement's level is what we compare against. $statementLevel = $tokens[ $statementToken ]['level']; + $hasUnconditionalAssignment = false; + $hasAnyAssignment = false; + // Search from the function start to the statement. for ( $i = $scopeOpener + 1; $i < $statementToken; $i++ ) { if ( $tokens[ $i ]['code'] !== T_VARIABLE ) { @@ -380,18 +383,118 @@ private function wasVariableUnconditionallyAssigned( File $phpcsFile, $functionT continue; } + $hasAnyAssignment = true; + // Check if the assignment is at the same scope level as the statement. // This ensures the variable was assigned unconditionally before the statement. $assignmentLevel = $tokens[ $i ]['level']; - if ( $assignmentLevel === $statementLevel ) { - return true; + if ( $assignmentLevel !== $statementLevel ) { + continue; + } + + // Even if levels match, check if this assignment is inside a conditional block. + // If it is, the variable might not be set. + if ( $this->isInsideConditionalBlock( $phpcsFile, $functionToken, $i ) ) { + continue; + } + + $hasUnconditionalAssignment = true; + } + + // If there's any assignment but no unconditional one, the variable might not be set. + // Only flag if we found an unconditional assignment. + return $hasUnconditionalAssignment; + } + + /** + * Check if a token position is inside a conditional block (if/else/elseif). + * + * @param File $phpcsFile The file being scanned. + * @param int $functionToken The position of the containing function. + * @param int $tokenPtr The position to check. + * + * @return bool True if inside a conditional block, false otherwise. + */ + private function isInsideConditionalBlock( File $phpcsFile, $functionToken, $tokenPtr ) { + $tokens = $phpcsFile->getTokens(); + $scopeOpener = $tokens[ $functionToken ]['scope_opener']; + + // Walk backwards from the token to find if it's inside an if/else/elseif block. + for ( $i = $tokenPtr - 1; $i > $scopeOpener; $i-- ) { + $code = $tokens[ $i ]['code']; + + // Check for opening braces that belong to if/else/elseif. + if ( $code === T_OPEN_CURLY_BRACKET ) { + // Check if the token is inside this brace's scope. + if ( ! isset( $tokens[ $i ]['bracket_closer'] ) ) { + continue; + } + + $closer = $tokens[ $i ]['bracket_closer']; + + if ( $tokenPtr <= $i || $tokenPtr >= $closer ) { + // Token is not inside this brace pair. + continue; + } + + // Find what this brace belongs to. + $owner = $this->findBraceOwner( $phpcsFile, $i ); + + if ( false === $owner ) { + continue; + } + + $ownerCode = $tokens[ $owner ]['code']; + + // If this brace belongs to an if/else/elseif, the token is inside a conditional. + if ( in_array( $ownerCode, array( T_IF, T_ELSE, T_ELSEIF ), true ) ) { + return true; + } } } return false; } + /** + * Find the owner of an opening brace (if, else, function, etc.). + * + * @param File $phpcsFile The file being scanned. + * @param int $bracePtr The position of the opening brace. + * + * @return false|int The position of the owner token, or false if not found. + */ + private function findBraceOwner( File $phpcsFile, $bracePtr ) { + $tokens = $phpcsFile->getTokens(); + + // Check if the brace has a recorded owner. + if ( isset( $tokens[ $bracePtr ]['scope_condition'] ) ) { + return $tokens[ $bracePtr ]['scope_condition']; + } + + // Look backwards for the owner. + $prevToken = $phpcsFile->findPrevious( T_WHITESPACE, $bracePtr - 1, null, true ); + + if ( false === $prevToken ) { + return false; + } + + $code = $tokens[ $prevToken ]['code']; + + // Direct owners (else, do, try, etc.). + if ( in_array( $code, array( T_ELSE, T_DO, T_TRY, T_FINALLY ), true ) ) { + return $prevToken; + } + + // Check for closing parenthesis (if, elseif, while, for, foreach, switch, catch). + if ( $code === T_CLOSE_PARENTHESIS && isset( $tokens[ $prevToken ]['parenthesis_owner'] ) ) { + return $tokens[ $prevToken ]['parenthesis_owner']; + } + + return false; + } + /** * Find the if/elseif statement that contains the given token. * diff --git a/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/SimplifyDualConditionToTernarySniff.php b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/SimplifyDualConditionToTernarySniff.php index 659f993dd6..979e2ed59a 100644 --- a/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/SimplifyDualConditionToTernarySniff.php +++ b/phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/SimplifyDualConditionToTernarySniff.php @@ -110,6 +110,11 @@ public function register() { public function process( File $phpcsFile, $stackPtr ) { $tokens = $phpcsFile->getTokens(); + // Skip if the statement already contains a ternary operator. + if ( $this->statementContainsTernary( $phpcsFile, $stackPtr ) ) { + return; + } + // Find the closing paren before || (end of first group). $leftGroupEnd = $phpcsFile->findPrevious( T_WHITESPACE, $stackPtr - 1, null, true ); @@ -478,4 +483,49 @@ private function getTokensContent( File $phpcsFile, $start, $end ) { return $content; } + + /** + * Check if the statement containing the || already has a ternary operator. + * + * @param File $phpcsFile The file being scanned. + * @param int $stackPtr The position of the || token. + * + * @return bool True if the statement already contains a ternary. + */ + private function statementContainsTernary( File $phpcsFile, $stackPtr ) { + $tokens = $phpcsFile->getTokens(); + + // Find the start of the statement (look for = or semicolon or opening brace). + $statementStart = $stackPtr; + + for ( $i = $stackPtr - 1; $i >= 0; $i-- ) { + $code = $tokens[ $i ]['code']; + + if ( $code === T_SEMICOLON || $code === T_OPEN_CURLY_BRACKET || $code === T_CLOSE_CURLY_BRACKET ) { + $statementStart = $i + 1; + break; + } + + if ( $code === T_OPEN_TAG ) { + $statementStart = $i; + break; + } + } + + // Find the end of the statement. + $statementEnd = $phpcsFile->findNext( T_SEMICOLON, $stackPtr + 1 ); + + if ( false === $statementEnd ) { + $statementEnd = count( $tokens ) - 1; + } + + // Check if there's a ternary operator (T_INLINE_THEN) in the statement. + for ( $i = $statementStart; $i <= $statementEnd; $i++ ) { + if ( $tokens[ $i ]['code'] === T_INLINE_THEN ) { + return true; + } + } + + return false; + } }