Skip to content

Commit 0dc6886

Browse files
noelsaw1claude
andcommitted
Add FP guard cases to test fixtures for N+1, limit-multiplier, admin-hook
- n-plus-one-optimized.php: sequential meta calls after loop closure - limit-multiplier-from-count.php: display/comparison/assignment count() uses - admin-no-capability.php: admin-only hook whitelist (admin_notices, admin_init, admin_menu) All 20 fixture validations pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ff2af3e commit 0dc6886

4 files changed

Lines changed: 129 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ All notable changes to this project will be documented in this file.
2020

2121
- N+1 pattern findings now include the actual source code line in the report. Previously the `code` field was empty because `find_meta_in_loop_line` only returned the line number without extracting the source text
2222

23+
### Tests
24+
25+
- Added false-positive guard cases to `n-plus-one-optimized.php` fixture: sequential `get_user_meta()` calls after loop closure should not be flagged
26+
- Expanded `limit-multiplier-from-count.php` fixture with display, comparison, and assignment uses of `count()` that should not match the multiplier pattern
27+
- Added admin-only hook whitelist cases to `admin-no-capability.php` fixture: `admin_notices`, `admin_init`, `admin_menu` hooks should be INFO, not HIGH
28+
2329
### Documentation
2430

2531
- Added "WP Code Check Scanner — Quick Reference" section to `AGENTS.md` with CLI flags, MCP server configuration, output locations, and pattern library pointer for AI agent discoverability

dist/tests/fixtures/admin-no-capability.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,37 @@ function check_user_capability( $user_id ) {
147147
return true;
148148
}
149149

150+
// ============================================================
151+
// ADMIN-ONLY HOOK WHITELIST - Should be INFO, not HIGH
152+
// These hooks inherently require admin context
153+
// ============================================================
154+
155+
// add_action with admin_notices hook - should be downgraded to INFO
156+
// This was the exact FP from creditconnection2-self-service credit-registry-forms.php:48
157+
function check_plugin_dependencies() {
158+
if (!is_plugin_active('required-plugin/required-plugin.php')) {
159+
add_action('admin_notices', 'show_dependency_notice');
160+
deactivate_plugins(plugin_basename(__FILE__));
161+
return false;
162+
}
163+
return true;
164+
}
165+
166+
function show_dependency_notice() {
167+
echo '<div class="notice notice-error"><p>Required plugin is not active.</p></div>';
168+
}
169+
170+
// add_action with admin_init hook - should be downgraded to INFO
171+
add_action( 'admin_init', 'register_plugin_settings' );
172+
173+
function register_plugin_settings() {
174+
register_setting( 'my_plugin_options', 'my_plugin_setting' );
175+
}
176+
177+
// add_action with admin_menu hook - should be downgraded to INFO
178+
add_action( 'admin_menu', 'add_plugin_admin_menu' );
179+
180+
function add_plugin_admin_menu() {
181+
add_options_page( 'Plugin Settings', 'Plugin Settings', 'manage_options', 'my-plugin', 'render_settings' );
182+
}
183+

dist/tests/fixtures/limit-multiplier-from-count.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,51 @@
22

33
// Fixture: candidate limit multiplier derived from count()
44

5+
// ============================================================
6+
// TRUE POSITIVE — count() multiplied into a limit value
7+
// ============================================================
8+
59
function hcc_fixture_limit_multiplier_from_count( array $user_ids ) {
610
$candidate_limit = count( $user_ids ) * 10 * 5;
711
return $candidate_limit;
812
}
13+
14+
// ============================================================
15+
// FALSE POSITIVE GUARDS — count() used for display, comparison, or assignment
16+
// These should NOT be flagged by limit-multiplier-from-count
17+
// ============================================================
18+
19+
// FP: count() used in echo/display context
20+
function display_pending_count( $pending ) {
21+
echo "\nPending submissions: " . count($pending) . "\n";
22+
}
23+
24+
// FP: count() used as array value for logging/display
25+
function build_summary_array( $person ) {
26+
return [
27+
'Phone_Count' => isset($person['ContactPhoneNumbers']) ? count($person['ContactPhoneNumbers']) : 0,
28+
'Email_Count' => isset($person['ContactEmailAddresses']) ? count($person['ContactEmailAddresses']) : 0,
29+
];
30+
}
31+
32+
// FP: count() used in while-loop comparison (not a LIMIT multiplier)
33+
function normalize_array( $normalized, $length ) {
34+
while (count($normalized) < $length) {
35+
$normalized[] = '';
36+
}
37+
return $normalized;
38+
}
39+
40+
// FP: count() used in HTML output
41+
function render_log_summary( $request_files, $response_files ) {
42+
echo '<p><strong>Request Logs:</strong> ' . count($request_files) . ' file(s)</p>';
43+
echo '<p><strong>Response Logs:</strong> ' . count($response_files) . ' file(s)</p>';
44+
}
45+
46+
// FP: count() used in if-condition (not multiplied)
47+
function trim_submissions( $pending_submissions ) {
48+
if (count($pending_submissions) > 10) {
49+
$pending_submissions = array_slice($pending_submissions, 0, 10);
50+
}
51+
return $pending_submissions;
52+
}

dist/tests/fixtures/n-plus-one-optimized.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,51 @@ function get_order_counts_for_customers( $user_ids ) {
154154
return $counts;
155155
}
156156

157+
// ============================================================
158+
// FALSE POSITIVE GUARD: Sequential meta calls AFTER loop closure
159+
// These should NOT be flagged as N+1 — the meta calls are outside the loop body
160+
// ============================================================
161+
162+
/**
163+
* FP CASE: get_user_meta() called sequentially for a single user AFTER a loop.
164+
* The loop iterates over something else; the meta read is not inside it.
165+
* This was the exact pattern from creditconnection2-self-service check-user-meta.php:23
166+
*/
167+
function process_users_then_read_single_meta( $users ) {
168+
// Loop over users for some unrelated work
169+
foreach ( $users as $user ) {
170+
echo esc_html( $user->display_name );
171+
}
172+
173+
// Sequential meta read for a single user — NOT inside the loop
174+
$current_user_id = get_current_user_id();
175+
$registry_id = get_user_meta( $current_user_id, 'creditregistry_id', true );
176+
if ( empty( $registry_id ) ) {
177+
$registry_id = get_user_meta( $current_user_id, 'credit_registry_id', true );
178+
}
179+
180+
return $registry_id;
181+
}
182+
183+
/**
184+
* FP CASE: Single get_user_meta() re-read after processing loop results.
185+
* This was the exact pattern from class-cr-business-rest-api.php:245
186+
*/
187+
function fetch_data_after_loop( $submissions ) {
188+
foreach ( $submissions as $key => $submission ) {
189+
$status = $submission['status'] ?? 'pending';
190+
if ( $status === 'success' ) {
191+
break;
192+
}
193+
}
194+
195+
// Re-read meta after loop — not inside loop body
196+
$user_id = get_current_user_id();
197+
$updated_id = get_user_meta( $user_id, 'cr_business_subscriber_registry_id', true );
198+
199+
return $updated_id;
200+
}
201+
157202
/**
158203
* Helper: Bulk load recent orders (example implementation)
159204
*/

0 commit comments

Comments
 (0)