Skip to content

New sniff to prevent redundant empty calls on defined object properties#2970

Merged
Crabcyborg merged 2 commits into
masterfrom
new_sniff_to_prevent_redundant_empty_calls_on_object_properties
Feb 24, 2026
Merged

New sniff to prevent redundant empty calls on defined object properties#2970
Crabcyborg merged 2 commits into
masterfrom
new_sniff_to_prevent_redundant_empty_calls_on_object_properties

Conversation

@Crabcyborg
Copy link
Copy Markdown
Contributor

@Crabcyborg Crabcyborg commented Feb 24, 2026

Summary by CodeRabbit

  • Chores
    • Refactored internal code to use direct property truthiness checks instead of explicit empty() function calls, improving code consistency and maintainability across the application.
    • Added automated code analysis tooling to enforce consistent property checking patterns going forward.

@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Feb 24, 2026

DeepSource Code Review

We reviewed changes in d3a993b...b850ec1 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Code Review Summary

Analyzer Status Updated (UTC) Details
PHP Feb 24, 2026 1:27p.m. Review ↗
JavaScript Feb 24, 2026 1:27p.m. Review ↗

@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Feb 24, 2026

DeepSource Code Review

We reviewed changes in d3a993b...675e375 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade  

Focus Area: Security
Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
PHP Feb 24, 2026 1:32p.m. Review ↗
JavaScript Feb 24, 2026 1:32p.m. Review ↗

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

This PR replaces explicit empty() checks with direct boolean truthiness checks across 18+ model, helper, and view classes, and introduces a new PHP CodeSniffer sniff to automatically detect and fix redundant empty() patterns on class-defined properties.

Changes

Cohort / File(s) Summary
Model Classes with Truthiness Refactoring
classes/models/FrmAddon.php, classes/models/FrmEmail.php, classes/models/FrmEntryValues.php, classes/models/FrmFieldValue.php, classes/models/FrmFieldValueSelector.php, classes/models/FrmFormApi.php, classes/models/FrmFormMigrator.php, classes/models/FrmSettings.php, classes/models/FrmSolution.php, classes/models/FrmValidate.php, classes/models/fields/FrmFieldType.php
Replaced empty($var) checks with ! $var or direct truthiness checks; pattern applied consistently across constructor initialization, conditional branches, and validation logic.
Helper Classes with Truthiness Refactoring
classes/helpers/FrmFieldGridHelper.php, classes/helpers/FrmListHelper.php, stripe/helpers/FrmStrpLiteLinkRedirectHelper.php
Replaced empty() checks with direct boolean truthiness; affects early returns and conditional field/section handling across grid and list display logic.
Entry and Field-Related Classes
classes/models/FrmCreateFile.php, classes/models/FrmEntryShortcodeFormatter.php, classes/models/FrmFieldFormHtml.php, classes/views/styles/components/FrmStyleComponent.php
Simplified conditions from ! empty($var) to direct truthiness checks; affects error display, form processing, and style component rendering.
CodeSniffer Sniff Implementation
phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php, phpcs-sniffs/Formidable/ruleset.xml
Added new sniff class with ~480 lines to detect and automatically fix redundant empty() calls on class properties; includes helpers for property validation, context analysis, and fix application; registered in ruleset.
Test File Updates
tests/phpunit/entries/test_FrmShowEntryShortcode.php
Updated conditional checks to use boolean truthiness instead of empty() calls in test assertion logic.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Simplify some array checks #2722: Implements the same refactoring pattern—replacing empty() checks with direct truthiness checks—across overlapping files like FrmListHelper, FrmFormApi, and FrmSolution.
  • Add new sniff to simplify truthy checks #2751: Performs an identical codebase-wide refactoring of empty() checks and introduces the same PHP CodeSniffer sniff infrastructure for automated detection.

Poem

🐰 With empty() gone and truthiness now clear,
Direct checks hop through the code without fear,
A sniff keeps watch for redundancies veiled,
In eighteen files, our refactor prevailed!
–Your friendly rabbit coder 🥕✨

🚥 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 clearly and concisely summarizes the main change: introducing a new PHP_CodeSniffer sniff to detect and fix redundant empty() calls on class-defined properties.
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 (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new_sniff_to_prevent_redundant_empty_calls_on_object_properties

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 7dd3b7a into master Feb 24, 2026
18 of 20 checks passed
@Crabcyborg Crabcyborg deleted the new_sniff_to_prevent_redundant_empty_calls_on_object_properties branch February 24, 2026 13:41
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
classes/models/FrmFieldValueSelector.php (1)

233-235: has_options() still uses ! empty() on the same property — inconsistent with the PR's pattern shift.

display_dropdown() at Line 285 was updated to if ( $this->options ), but has_options() (which guards display_dropdown()) still uses ! empty( $this->options ). The new sniff should flag this as well.

♻️ Proposed fix
 final protected function has_options() {
-    return ! empty( $this->options );
+    return (bool) $this->options;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@classes/models/FrmFieldValueSelector.php` around lines 233 - 235, The method
has_options() still uses "! empty( $this->options )" which is inconsistent with
the new pattern used in display_dropdown(); change has_options() to check the
property directly and return a boolean, e.g. update final protected function
has_options() to return (bool) $this->options; so it matches the new "if (
$this->options )" style and keeps the method's boolean contract.
phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php (2)

230-248: findContainingClass misses anonymous classes and produces false negatives for traits.

The method only matches T_CLASS, so empty($this->prop) inside an anonymous class (T_ANON_CLASS) will never be flagged. Traits can also declare typed instance properties; those are silently skipped as well.

Consider adding T_ANON_CLASS to the check and, optionally, T_TRAIT:

♻️ Proposed fix
-            if ( $tokens[ $i ]['code'] !== T_CLASS ) {
+            if ( ! in_array( $tokens[ $i ]['code'], array( T_CLASS, T_ANON_CLASS ), true ) ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php`
around lines 230 - 248, findContainingClass currently only looks for tokens with
code T_CLASS, so anonymous classes and traits are missed; update the token check
in findContainingClass to consider T_ANON_CLASS (and optionally T_TRAIT) in
addition to T_CLASS, ensuring the same scope_opener/scope_closer checks and the
existing containment test ($stackPtr > scope_opener && $stackPtr < scope_closer)
are applied to those token types so anonymous classes and trait declarations are
correctly detected.

416-416: count( $tokens ) re-evaluated on every loop iteration.

count() on a plain PHP array is O(1), but calling it in the while condition is still unnecessary work and non-idiomatic.

♻️ Proposed fix
-        while ( $nextPtr < count( $tokens ) ) {
+        $token_count = count( $tokens );
+        while ( $nextPtr < $token_count ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php`
at line 416, The while loop condition repeatedly calls count($tokens); to fix,
compute the length once before the loop (e.g. $tokenCount = count($tokens);) and
replace the condition in the while (currently "while ( $nextPtr < count( $tokens
) )") with "while ( $nextPtr < $tokenCount )"; update any inner code that may
modify $tokens to adjust $tokenCount if necessary, and apply this change inside
the RedundantEmptyOnDefinedPropertySniff class/method where $nextPtr and $tokens
are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php`:
- Around line 266-301: isDefinedProperty currently treats T_STATIC as a
standalone trigger which causes static properties like "private static $foo" to
be matched as instance properties; change the logic so static declarations are
ignored by either removing T_STATIC from the initial trigger list or
(preferably) by scanning modifiers between $i and $varToken to detect T_STATIC
and skipping the match when found. Specifically, in the loop inside
isDefinedProperty (use symbols $i, $tokens, $varToken, $phpcsFile->findNext and
$propertyName), after locating the next T_VARIABLE but before returning true,
inspect the tokens between $i and $varToken for any T_STATIC and if present
continue the outer loop (i.e., do not treat it as a matching instance property).
Ensure behavior still handles sequences like "public static $x" and "static $x"
correctly by only matching when no T_STATIC modifier is present.

---

Nitpick comments:
In `@classes/models/FrmFieldValueSelector.php`:
- Around line 233-235: The method has_options() still uses "! empty(
$this->options )" which is inconsistent with the new pattern used in
display_dropdown(); change has_options() to check the property directly and
return a boolean, e.g. update final protected function has_options() to return
(bool) $this->options; so it matches the new "if ( $this->options )" style and
keeps the method's boolean contract.

In
`@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php`:
- Around line 230-248: findContainingClass currently only looks for tokens with
code T_CLASS, so anonymous classes and traits are missed; update the token check
in findContainingClass to consider T_ANON_CLASS (and optionally T_TRAIT) in
addition to T_CLASS, ensuring the same scope_opener/scope_closer checks and the
existing containment test ($stackPtr > scope_opener && $stackPtr < scope_closer)
are applied to those token types so anonymous classes and trait declarations are
correctly detected.
- Line 416: The while loop condition repeatedly calls count($tokens); to fix,
compute the length once before the loop (e.g. $tokenCount = count($tokens);) and
replace the condition in the while (currently "while ( $nextPtr < count( $tokens
) )") with "while ( $nextPtr < $tokenCount )"; update any inner code that may
modify $tokens to adjust $tokenCount if necessary, and apply this change inside
the RedundantEmptyOnDefinedPropertySniff class/method where $nextPtr and $tokens
are used.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3a993b and 675e375.

📒 Files selected for processing (22)
  • classes/helpers/FrmFieldGridHelper.php
  • classes/helpers/FrmListHelper.php
  • classes/models/FrmAddon.php
  • classes/models/FrmCreateFile.php
  • classes/models/FrmEmail.php
  • classes/models/FrmEntryShortcodeFormatter.php
  • classes/models/FrmEntryValues.php
  • classes/models/FrmFieldFormHtml.php
  • classes/models/FrmFieldValue.php
  • classes/models/FrmFieldValueSelector.php
  • classes/models/FrmFormApi.php
  • classes/models/FrmFormMigrator.php
  • classes/models/FrmSettings.php
  • classes/models/FrmSolution.php
  • classes/models/FrmTableHTMLGenerator.php
  • classes/models/FrmValidate.php
  • classes/models/fields/FrmFieldType.php
  • classes/views/styles/components/FrmStyleComponent.php
  • phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php
  • phpcs-sniffs/Formidable/ruleset.xml
  • stripe/helpers/FrmStrpLiteLinkRedirectHelper.php
  • tests/phpunit/entries/test_FrmShowEntryShortcode.php

Comment on lines +266 to +301
for ( $i = $classOpener + 1; $i < $classCloser; $i++ ) {
// Skip nested class/function scopes.
if ( in_array( $tokens[ $i ]['code'], array( T_FUNCTION, T_CLOSURE ), true ) ) {
if ( isset( $tokens[ $i ]['scope_closer'] ) ) {
$i = $tokens[ $i ]['scope_closer'];
}
continue;
}

// Look for property declarations: visibility modifier or var followed by $variable.
if ( ! in_array( $tokens[ $i ]['code'], array( T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_STATIC ), true ) ) {
continue;
}

// Find the next variable token after the modifier.
$varToken = $phpcsFile->findNext(
array( T_WHITESPACE, T_STATIC, T_READONLY, T_STRING, T_NULLABLE, T_TYPE_UNION, T_TYPE_INTERSECTION, T_NULL, T_SELF, T_PARENT, T_ARRAY, T_CALLABLE, T_NS_SEPARATOR ),
$i + 1,
$classCloser,
true
);

if ( false === $varToken || $tokens[ $varToken ]['code'] !== T_VARIABLE ) {
continue;
}

// Check if the variable name matches (strip the $).
$varName = ltrim( $tokens[ $varToken ]['content'], '$' );

if ( $varName === $propertyName ) {
return true;
}
}

return false;
}
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 | 🟡 Minor

isDefinedProperty incorrectly matches static property declarations as instance properties.

T_STATIC appears in both the trigger list (T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_STATIC) and the findNext skip list. For a declaration like private static $foo = []:

  • When $i lands on T_PRIVATE, findNext skips T_WHITESPACE and T_STATIC, then returns T_VARIABLE ($foo) — match.
  • When $i lands on T_STATIC itself, findNext also finds $foo — match again.

If a class has only private static $foo (no instance $foo), the sniff will flag empty( $this->foo ) as redundant and produce a "fix". Because $this->foo accesses an undefined instance property, the autofix doesn't break runtime behaviour (both empty() and the truthiness check trigger the same deprecation or error in PHP 8.2+), but the detection is semantically incorrect.

🔧 Proposed fix — skip static property declarations
         if ( false === $varToken || $tokens[ $varToken ]['code'] !== T_VARIABLE ) {
             continue;
         }

+        // Skip static properties: $this->prop accesses instance scope, not static scope.
+        $is_static_property = false;
+        for ( $j = $i; $j < $varToken; $j++ ) {
+            if ( $tokens[ $j ]['code'] === T_STATIC ) {
+                $is_static_property = true;
+                break;
+            }
+        }
+        if ( $is_static_property ) {
+            $i = $varToken;
+            continue;
+        }
+
         // Check if the variable name matches (strip the $).
         $varName = ltrim( $tokens[ $varToken ]['content'], '$' );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@phpcs-sniffs/Formidable/Sniffs/CodeAnalysis/RedundantEmptyOnDefinedPropertySniff.php`
around lines 266 - 301, isDefinedProperty currently treats T_STATIC as a
standalone trigger which causes static properties like "private static $foo" to
be matched as instance properties; change the logic so static declarations are
ignored by either removing T_STATIC from the initial trigger list or
(preferably) by scanning modifiers between $i and $varToken to detect T_STATIC
and skipping the match when found. Specifically, in the loop inside
isDefinedProperty (use symbols $i, $tokens, $varToken, $phpcsFile->findNext and
$propertyName), after locating the next T_VARIABLE but before returning true,
inspect the tokens between $i and $varToken for any T_STATIC and if present
continue the outer loop (i.e., do not treat it as a matching instance property).
Ensure behavior still handles sequences like "public static $x" and "static $x"
correctly by only matching when no T_STATIC modifier is present.

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