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
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand All @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

Expand Down Expand Up @@ -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;
}
}
Loading