Add new sniff to remove unecessary empty line in short three line fun…#2761
Conversation
WalkthroughThis PR performs a widespread whitespace cleanup, removing unnecessary blank lines in short functions across the codebase and adding a new PHPCS sniff to enforce this pattern. However, it introduces critical bugs in Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.php:
- Around line 67-81: The semicolon counting in ShortFunctionBlankLineSniff
currently iterates from $openBrace to $closeBrace and counts every T_SEMICOLON,
which miscounts semicolons inside nested constructs; update the loop to track a
$nestingLevel (increment on
T_OPEN_PARENTHESIS/T_OPEN_SQUARE_BRACKET/T_OPEN_CURLY_BRACKET, decrement on
corresponding close tokens) and only increment $statementCount and push into
$semicolons when $tokens[$i]['code'] === T_SEMICOLON and $nestingLevel === 0,
following the pattern used in ConsecutiveAssignmentSpacingSniff to ensure only
top-level statements are counted.
🧹 Nitpick comments (2)
phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.php (2)
63-65: Consider documenting the specific code style assumption.The check for exactly 4 lines assumes a very specific function structure (opening brace on separate line, closing brace on separate line). While this is intentional and prevents false positives, it means the sniff won't catch similar issues in functions formatted differently (e.g., opening brace on same line as function declaration).
This is a design choice rather than a bug, but consider adding a code example in the class docblock to clarify which function styles are supported.
111-120: Indentation extraction could be more defensive.The logic assumes the token before the second statement is whitespace when extracting indentation. While this should be true for the pattern this sniff detects, consider adding a fallback to detect indentation more robustly:
- If
$tokens[$secondStatement - 1]is not whitespace, you could look further back- Or you could extract indentation from the first statement instead, assuming both should have the same indentation
This is a minor edge case and unlikely to cause issues in practice, but would make the fixer more resilient.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
phpcs.xmlis excluded by!**/*.xml
📒 Files selected for processing (32)
classes/controllers/FrmAddonsController.phpclasses/controllers/FrmEntriesController.phpclasses/controllers/FrmFieldsController.phpclasses/controllers/FrmFormsController.phpclasses/controllers/FrmSettingsController.phpclasses/controllers/FrmStylesController.phpclasses/factories/FrmFieldFactory.phpclasses/helpers/FrmAppHelper.phpclasses/helpers/FrmEntriesHelper.phpclasses/helpers/FrmFieldsHelper.phpclasses/helpers/FrmFormsHelper.phpclasses/helpers/FrmListHelper.phpclasses/helpers/FrmStylesHelper.phpclasses/helpers/FrmSubmitHelper.phpclasses/models/FrmAddon.phpclasses/models/FrmDb.phpclasses/models/FrmEmail.phpclasses/models/FrmEntry.phpclasses/models/FrmEntryFormatter.phpclasses/models/FrmEntryValidate.phpclasses/models/FrmField.phpclasses/models/FrmFieldFormHtml.phpclasses/models/FrmForm.phpclasses/models/FrmFormAction.phpclasses/models/FrmPersonalData.phpclasses/models/fields/FrmFieldTextarea.phpclasses/models/fields/FrmFieldType.phpclasses/models/fields/FrmFieldUserID.phpphpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.phptests/phpunit/entries/test_FrmShowEntryShortcode.phptests/phpunit/fields/test_FrmFieldValidate.phptests/phpunit/forms/test_FrmFormsController.php
💤 Files with no reviewable changes (31)
- classes/models/fields/FrmFieldUserID.php
- classes/helpers/FrmFieldsHelper.php
- tests/phpunit/fields/test_FrmFieldValidate.php
- classes/models/FrmAddon.php
- classes/helpers/FrmFormsHelper.php
- classes/controllers/FrmEntriesController.php
- tests/phpunit/entries/test_FrmShowEntryShortcode.php
- classes/models/FrmEntryFormatter.php
- classes/controllers/FrmSettingsController.php
- tests/phpunit/forms/test_FrmFormsController.php
- classes/models/FrmFormAction.php
- classes/controllers/FrmAddonsController.php
- classes/factories/FrmFieldFactory.php
- classes/helpers/FrmListHelper.php
- classes/helpers/FrmAppHelper.php
- classes/helpers/FrmStylesHelper.php
- classes/helpers/FrmSubmitHelper.php
- classes/models/fields/FrmFieldType.php
- classes/models/FrmDb.php
- classes/controllers/FrmStylesController.php
- classes/models/FrmField.php
- classes/models/FrmForm.php
- classes/models/FrmEntry.php
- classes/controllers/FrmFieldsController.php
- classes/models/FrmPersonalData.php
- classes/models/FrmFieldFormHtml.php
- classes/controllers/FrmFormsController.php
- classes/models/FrmEmail.php
- classes/helpers/FrmEntriesHelper.php
- classes/models/fields/FrmFieldTextarea.php
- classes/models/FrmEntryValidate.php
⏰ 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). (9)
- GitHub Check: PHP 7.4 tests in WP 6.9
- GitHub Check: PHP 8 tests in WP 6.9
- 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
- GitHub Check: Cypress
🔇 Additional comments (1)
phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.php (1)
26-28: LGTM!The
register()method correctly returns theT_FUNCTIONtoken to listen for function declarations.
| // Count the number of statements (semicolons) in the function. | ||
| $statementCount = 0; | ||
| $semicolons = array(); | ||
|
|
||
| for ( $i = $openBrace + 1; $i < $closeBrace; $i++ ) { | ||
| if ( $tokens[ $i ]['code'] === T_SEMICOLON ) { | ||
| ++$statementCount; | ||
| $semicolons[] = $i; | ||
| } | ||
| } | ||
|
|
||
| // We only care about functions with exactly 2 statements. | ||
| if ( $statementCount !== 2 ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.phpRepository: Strategy11/formidable-forms
Length of output: 4757
🏁 Script executed:
# Check if PHPCS provides scope information for nested structures like for-loops
rg "nested_parenthesis|scope_opener|scope_closer" phpcs-sniffs/ -A 2 -B 2
# Also search for how other sniffs in the codebase handle scope checking
echo "=== Other sniffs in codebase ==="
find phpcs-sniffs -name "*.php" -type f | head -5 | xargs grep -l "scope" 2>/dev/nullRepository: Strategy11/formidable-forms
Length of output: 5762
🏁 Script executed:
# Search for how PHP_CodeSniffer handles nested structures
rg "parenthesis_owner|level|nested" phpcs-sniffs/ -B 2 -A 2
# Look for test files or examples
find . -name "*test*" -o -name "*Test*" | grep -i sniff | head -10
# Check PHP_CodeSniffer documentation or usage patterns
rg "findNext|findPrevious" phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.php -B 2 -A 2Repository: Strategy11/formidable-forms
Length of output: 5245
🏁 Script executed:
# Search for how token level is used in the codebase
rg "tokens\[.*\]\['level'\]" phpcs-sniffs/ | head -20
# Check if nested_parenthesis actually exists in token properties
rg "nested_parenthesis|parenthesis_owner" phpcs-sniffs/
# Look at how tokens are typically structured
echo "=== Checking token structure documentation ==="
rg "T_SEMICOLON" phpcs-sniffs/ -B 3 -A 3 | head -40Repository: Strategy11/formidable-forms
Length of output: 4221
🏁 Script executed:
# Examine RedundantEmptyOnAssignedVariableSniff to see how it uses level
cat -n phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnAssignedVariableSniff.php | sed -n '100,150p'
# Look for examples in ConsecutiveAssignmentSpacingSniff to see the nesting approach
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php | sed -n '60,100p'
# Verify if token level would correctly identify nested semicolons
echo "=== Check what 'level' property represents ==="
rg "'level'" phpcs-sniffs/ -B 5 -A 2 | head -60Repository: Strategy11/formidable-forms
Length of output: 5682
🏁 Script executed:
# Create test PHP code to verify the behavior
cat > /tmp/test_function.php << 'EOF'
<?php
// Test case 1: For loop - would have 3 semicolons
function testForLoop() {
for ($i = 0; $i < 10; $i++);
return 1;
}
// Test case 2: Simple closure - would have 2 semicolons at right level
function testClosure() {
$fn = function() { return 1; };
return 2;
}
// Test case 3: Do-while - would have 2 semicolons
function testDoWhile() {
do { } while (false);
return 1;
}
// Test case 4: Two simple statements - correct case
function testTwoStatements() {
$x = 1;
return $x;
}
EOF
# Count semicolons in each function to understand what the sniff would count
python3 << 'PYTHON'
import re
with open('/tmp/test_function.php', 'r') as f:
content = f.read()
# Extract each function
functions = re.findall(r'function\s+(\w+)\s*\(\)[^{]*\{([^}]+)\}', content)
for func_name, body in functions:
semicolon_count = body.count(';')
line_count = body.count('\n') + 1
print(f"{func_name}:")
print(f" Semicolons in body: {semicolon_count}")
print(f" Lines in body: {line_count}")
print(f" Body: {repr(body)}")
print()
PYTHONRepository: Strategy11/formidable-forms
Length of output: 157
🏁 Script executed:
# Analyze the line counting constraint more carefully
echo "=== Understanding the 4-line constraint ==="
echo "Function structure that needs exactly 4 lines:"
cat << 'EOF'
Line 1: {
Line 2: statement 1;
Line 3: [blank line]
Line 4: statement 2;
Line 5: }
Result: closeLine - openLine = 5 - 1 = 4 ✓
EOF
echo ""
echo "=== For-loop scenarios ==="
echo "For loop: for (\$i = 0; \$i < 10; \$i++); would have 3 semicolons → rejected by line 79"
echo ""
echo "Empty closure: \$fn = function() {}; return 1; = 2 semicolons"
echo "But closure body is on same line, body spans multiple lines"
# Check if there are any tests or examples showing expected behavior
find . -type f -name "*.php" | xargs rg -l "ShortFunctionBlankLineSniff" 2>/dev/null | head -5
# Look for any test data
find . -type d -name "*test*" | head -10Repository: Strategy11/formidable-forms
Length of output: 659
🏁 Script executed:
# Check the exact usage pattern in the codebase for level-based filtering
cat -n phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnAssignedVariableSniff.php | sed -n '200,250p'
# Look for the opening brace level in the sniff file
rg "level" phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.php -B 2 -A 2
# Find examples in ConsecutiveAssignmentSpacingSniff to verify the nestingLevel approach
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php | sed -n '150,200p'Repository: Strategy11/formidable-forms
Length of output: 3584
🏁 Script executed:
# Look at how level is used to filter at the same scope
cat -n phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnAssignedVariableSniff.php | sed -n '258,280p'
# Check if there's documentation about token level
rg "scope_opener.*level|level.*scope" phpcs-sniffs/ -B 3 -A 3Repository: Strategy11/formidable-forms
Length of output: 1214
🏁 Script executed:
# Look at the ConsecutiveAssignmentSpacingSniff approach more carefully
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php | sed -n '320,360p'
# Check what the actual sniff returns - look for the findAssignmentEnd function
rg "findAssignmentEnd|findPrevious.*SEMICOLON" phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php -B 3 -A 15 | head -50Repository: Strategy11/formidable-forms
Length of output: 2665
🏁 Script executed:
# Get the complete findAssignmentEnd function
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php | sed -n '434,480p'Repository: Strategy11/formidable-forms
Length of output: 1636
🏁 Script executed:
# Get the complete findAssignmentEnd function
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php | sed -n '405,432p'Repository: Strategy11/formidable-forms
Length of output: 989
🏁 Script executed:
# Get the complete findAssignmentEnd function
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php | sed -n '370,407p'Repository: Strategy11/formidable-forms
Length of output: 1365
🏁 Script executed:
# Search PHPCS documentation or examples in repo for nested_parenthesis
rg "nested_parenthesis|parenthesis_opener|parenthesis_closer" phpcs-sniffs/ -l
# Verify the nestingLevel approach is the correct pattern used in codebase
echo "=== Pattern found in ConsecutiveAssignmentSpacingSniff ==="
cat -n phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ConsecutiveAssignmentSpacingSniff.php | sed -n '387,402p'Repository: Strategy11/formidable-forms
Length of output: 1275
Statement counting doesn't respect nesting boundaries.
The semicolon counting logic iterates through all tokens in the function body without checking their nesting level. This means it counts semicolons inside nested structures:
- Closures:
$fn = function() { return 1; };contains 2 semicolons at different nesting levels - Nested parentheses in expressions:
$x = (isset($a) ? $a : 0);may contain semicolons in ternary or other nested contexts - Do-while loops:
do { ... } while (condition);
This could cause false positives where the sniff incorrectly flags valid functions containing these constructs.
The codebase already demonstrates the correct approach in ConsecutiveAssignmentSpacingSniff using a nestingLevel counter:
$nestingLevel = 0;
for ( $i = $openBrace + 1; $i < $closeBrace; $i++ ) {
$code = $tokens[ $i ]['code'];
if ( in_array( $code, array( T_OPEN_PARENTHESIS, T_OPEN_SQUARE_BRACKET, T_OPEN_CURLY_BRACKET ), true ) ) {
++$nestingLevel;
} elseif ( in_array( $code, array( T_CLOSE_PARENTHESIS, T_CLOSE_SQUARE_BRACKET, T_CLOSE_CURLY_BRACKET ), true ) ) {
--$nestingLevel;
} elseif ( $tokens[ $i ]['code'] === T_SEMICOLON && $nestingLevel === 0 ) {
++$statementCount;
$semicolons[] = $i;
}
}Apply this same pattern to count only top-level statements in the function.
🤖 Prompt for AI Agents
In @phpcs-sniffs/Formidable/Sniffs/WhiteSpace/ShortFunctionBlankLineSniff.php
around lines 67 - 81, The semicolon counting in ShortFunctionBlankLineSniff
currently iterates from $openBrace to $closeBrace and counts every T_SEMICOLON,
which miscounts semicolons inside nested constructs; update the loop to track a
$nestingLevel (increment on
T_OPEN_PARENTHESIS/T_OPEN_SQUARE_BRACKET/T_OPEN_CURLY_BRACKET, decrement on
corresponding close tokens) and only increment $statementCount and push into
$semicolons when $tokens[$i]['code'] === T_SEMICOLON and $nestingLevel === 0,
following the pattern used in ConsecutiveAssignmentSpacingSniff to ensure only
top-level statements are counted.
…y_whitespace_in_short_three_line_functions Add new sniff to remove unecessary empty line in short three line fun…
…ctions