Skip to content

Improve early return sniff negating logic#2819

Merged
Crabcyborg merged 1 commit into
masterfrom
improve_early_return_sniff_negating
Jan 16, 2026
Merged

Improve early return sniff negating logic#2819
Crabcyborg merged 1 commit into
masterfrom
improve_early_return_sniff_negating

Conversation

@Crabcyborg

@Crabcyborg Crabcyborg commented Jan 16, 2026

Copy link
Copy Markdown
Contributor

The previous logic broke a condition.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of compound logical conditions in code analysis to correctly apply negation patterns and prevent false positives in refactoring suggestions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

A private helper method hasTopLevelOperator() was added to detect top-level logical operators in conditions. The negateCondition() method was refactored to handle compound conditions by wrapping negation with parentheses, while preserving existing behavior for simple conditions that may flip comparison operators.

Changes

Cohort / File(s) Change Summary
Condition Negation Enhancement
phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php
Added private helper method hasTopLevelOperator() to detect top-level && or || operators. Updated negateCondition() to differentiate between simple and compound conditions: compound conditions are negated by wrapping in parentheses ! ( condition ), while simple conditions maintain existing operator-flip logic or fall back to simple negation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #2803: Modifies the same sniff with focus on condition negation logic in negateCondition() method.
  • PR #2806: Updates FlipIfToEarlyReturnSniff with complementary helper logic for operator-flipping and improved condition negation handling.

Poem

🐰 A rabbit hops through logic's gate,
Where conditions bend and operators wait,
Simple or compound, now they're treated right,
Negation wrapped in parentheses bright! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve early return sniff negating logic' directly and accurately summarizes the main change, which focuses on improving the negation logic in the FlipIfToEarlyReturnSniff class.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Crabcyborg
Crabcyborg merged commit 43910b8 into master Jan 16, 2026
35 of 36 checks passed
@Crabcyborg
Crabcyborg deleted the improve_early_return_sniff_negating branch January 16, 2026 14:31

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php`:
- Around line 246-272: Update hasTopLevelOperator() to treat PHP word operators
("and", "or", "xor") as compound operators in addition to "&&" and "||" so the
FlipIfToEarlyReturnSniff no longer treats `$a and $b` as a simple condition;
specifically, when scanning the condition string in hasTopLevelOperator(),
detect top-level occurrences of the tokens and/or using word-boundary checks
(e.g. '\band\b', '\bor\b', '\bxor\b') while still respecting parentheses depth
and ignoring matches inside sub-expressions or strings, and return true when any
such top-level word operator is found so the caller (the $isCompound check) will
wrap compound conditions in parentheses before negation.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca0704 and 0be1981.

📒 Files selected for processing (1)
  • phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: - Do not make suggestions when we use x.x for a version placeholder. This is used like "@SInCE x.x" in a PHP doc comment, but also like 'x.x' when functions like _deprecated_function() are used.

  • Treat these placeholders as acceptable in this codebase.

Files:

  • phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php
🧬 Code graph analysis (1)
phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php (1)
phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipForeachIfToContinueSniff.php (1)
  • flipComparisonOperator (271-299)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: PHP 7.4 tests in WP 6.9
  • GitHub Check: PHP 8 tests in WP 6.9
  • GitHub Check: Cypress
  • GitHub Check: Cypress
  • GitHub Check: PHP 7.4 tests in WP 6.9
  • GitHub Check: PHP 8 tests in WP 6.9

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +246 to 272
// Check if this is a compound condition (has && or || at top level).
$isCompound = $this->hasTopLevelOperator( $condition );

// Only remove leading ! if it's a simple condition (not compound).
if ( ! $isCompound ) {
// If condition starts with !, remove it.
if ( strpos( $condition, '! ' ) === 0 ) {
return substr( $condition, 2 );
}

if ( strpos( $condition, '!' ) === 0 && strpos( $condition, '!=' ) !== 0 ) {
return substr( $condition, 1 );
}
if ( strpos( $condition, '!' ) === 0 && strpos( $condition, '!=' ) !== 0 ) {
return substr( $condition, 1 );
}

// Try to flip comparison operators.
$flipped = $this->flipComparisonOperator( $condition );
// Try to flip comparison operators.
$flipped = $this->flipComparisonOperator( $condition );

if ( $flipped !== false ) {
return $flipped;
}
if ( $flipped !== false ) {
return $flipped;
}

// Otherwise, add negation.
// If condition is simple (no spaces or operators at top level), just add !
// If complex, wrap in parentheses.
if ( $this->isSimpleCondition( $condition ) ) {
// Simple condition, just add !
return '! ' . $condition;
}

// For compound conditions, wrap in parentheses and negate.
return '! ( ' . $condition . ' )';

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:

cd /root/repo && head -300 phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php | tail -80

Repository: Strategy11/formidable-forms

Length of output: 131


🏁 Script executed:

cd /root/repo && sed -n '240,320p' phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php

Repository: Strategy11/formidable-forms

Length of output: 131


🌐 Web query:

PHP operator precedence: and vs && or vs || negation semantics

💡 Result:

Key points (from the PHP manual):

  • Precedence order (relevant subset): ! (logical NOT) → && → || → = (assignment) → and / xor / or. [1]

  • Practical consequences:

    • && and || bind tighter than assignment, so
      • $a = true && false; // parsed as $a = (true && false) → $a === false. [1]
    • and and or have lower precedence than assignment, so
      • $a = true and false; // parsed as ($a = true) and false → $a === true. [1]
  • Negation: the unary ! has higher precedence than the logical binary ops, so ! $x is evaluated before &&/||. Use parentheses when mixing negation with assignment or other operators to make intent explicit. [1]

Recommendation: always use parentheses when precedence might be unclear (or prefer &&/|| with explicit parentheses instead of and/or) to avoid surprising behavior. [1]

Source: PHP manual — Operator precedence. [1]


🏁 Script executed:

git ls-files | grep -i "flipif" | head -5

Repository: Strategy11/formidable-forms

Length of output: 220


🏁 Script executed:

sed -n '240,320p' phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php

Repository: Strategy11/formidable-forms

Length of output: 1908


🏁 Script executed:

sed -n '275,315p' phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php

Repository: Strategy11/formidable-forms

Length of output: 845


🏁 Script executed:

sed -n '1,100p' phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php | head -60

Repository: Strategy11/formidable-forms

Length of output: 1356


🏁 Script executed:

sed -n '100,200p' phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php

Repository: Strategy11/formidable-forms

Length of output: 3192


🏁 Script executed:

# Check if word operators appear in the test suite or examples to assess practical impact
git grep -n "and\|or\|xor" phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php

Repository: Strategy11/formidable-forms

Length of output: 6415


🏁 Script executed:

# Also check for test files
git ls-files | grep -i "test\|spec" | grep -i flip

Repository: Strategy11/formidable-forms

Length of output: 53


🏁 Script executed:

sed -n '318,375p' phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php

Repository: Strategy11/formidable-forms

Length of output: 1614


Extend hasTopLevelOperator() to detect word operators (and/or/xor).

The method currently checks only && and ||, missing word operators. Conditions like $a and $b are treated as simple and negated to ! $a and $b, which changes semantics due to PHP operator precedence (where ! binds tighter than and/or/xor, unlike &&/||). Use parentheses consistently by detecting all compound operators.

🔧 Suggested update to detect word operators
-        // Check for && or ||.
+        // Check for && or ||.
         if ( $i < $len - 1 ) {
             $twoChars = $condition[ $i ] . $condition[ $i + 1 ];

             if ( $twoChars === '&&' || $twoChars === '||' ) {
                 return true;
             }
         }
+
+        // Check for word operators (and/or/xor) with word boundaries.
+        if ( ctype_alpha( $char ) ) {
+            $remaining = substr( $condition, $i );
+            foreach ( array( 'and', 'or', 'xor' ) as $wordOp ) {
+                $lenOp = strlen( $wordOp );
+                if ( strncmp( $remaining, $wordOp, $lenOp ) === 0 ) {
+                    $before = $i === 0 ? ' ' : $condition[ $i - 1 ];
+                    $after  = $i + $lenOp >= $len ? ' ' : $condition[ $i + $lenOp ];
+                    if ( ! ctype_alnum( $before ) && $before !== '_' && ! ctype_alnum( $after ) && $after !== '_' ) {
+                        return true;
+                    }
+                }
+            }
+        }
🤖 Prompt for AI Agents
In `@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/FlipIfToEarlyReturnSniff.php`
around lines 246 - 272, Update hasTopLevelOperator() to treat PHP word operators
("and", "or", "xor") as compound operators in addition to "&&" and "||" so the
FlipIfToEarlyReturnSniff no longer treats `$a and $b` as a simple condition;
specifically, when scanning the condition string in hasTopLevelOperator(),
detect top-level occurrences of the tokens and/or using word-boundary checks
(e.g. '\band\b', '\bor\b', '\bxor\b') while still respecting parentheses depth
and ignoring matches inside sub-expressions or strings, and return true when any
such top-level word operator is found so the caller (the $isCompound check) will
wrap compound conditions in parentheses before negation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant