|
| 1 | +# PROJECT-DRY-2 — DRY Opportunities for `check-performance.sh` |
| 2 | +**Date:** 2026-06-13 |
| 3 | +**Scope:** Bash performance/security scanner (`dist/bin/check-performance.sh`) |
| 4 | +**Goal:** Highlight consolidation targets so the toolkit keeps a single source of truth for shared behaviors without touching runtime logic yet. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## 🎯 Summary |
| 9 | +- The performance/security scanner has grown a large collection of inline helpers and bespoke check blocks. Several of them duplicate responsibilities that already live (or could live) in `dist/bin/lib/*.sh`. |
| 10 | +- Consolidating JSON/reporting helpers, project metadata detection, and the bespoke grep/formatting loops will reduce maintenance risk and align the script with the project’s DRY/SOT expectations. |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## 🔁 Consolidation Targets |
| 15 | + |
| 16 | +### 1) JSON + Reporting Utilities |
| 17 | +**Severity:** 🟡 MEDIUM |
| 18 | +**Priority:** 🔵 HIGH |
| 19 | +**Effort:** ⚡ MEDIUM (4-6 hours) |
| 20 | + |
| 21 | +**Issue:** |
| 22 | +Functions such as `json_escape()`, `add_json_finding()`, `add_json_check()`, `output_json()`, and `generate_html_report()` are defined directly in the script even though `dist/bin/lib/json-helpers.sh` already exists as a shared JSON surface. Keeping these inline makes it easy for future scripts to re-implement similar logic inconsistently. |
| 23 | + |
| 24 | +**Impact:** |
| 25 | +- 🔴 **Maintenance Risk:** Any bug fix in JSON escaping must be duplicated across scripts |
| 26 | +- 🟡 **Inconsistency:** Future scripts may implement different JSON formatting |
| 27 | +- 🟢 **Testing:** Harder to unit test JSON generation in isolation |
| 28 | + |
| 29 | +**Recommendation:** |
| 30 | +Move all JSON construction/escaping and HTML report generation into `dist/bin/lib/json-helpers.sh` (or a new `report-helpers.sh`) and source it from the main script. Keep only minimal wiring in `check-performance.sh`. |
| 31 | + |
| 32 | +**Functions to Extract:** |
| 33 | +- `json_escape()` - 9 lines |
| 34 | +- `url_encode()` - 4 lines |
| 35 | +- `add_json_finding()` - 67 lines |
| 36 | +- `add_json_check()` - 12 lines |
| 37 | +- `output_json()` - 68 lines |
| 38 | +- `generate_html_report()` - ~100 lines |
| 39 | + |
| 40 | +**Total Lines to Move:** ~260 lines → `dist/bin/lib/report-helpers.sh` |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +### 2) Project Metadata + Version SOT |
| 45 | +**Severity:** 🔴 HIGH |
| 46 | +**Priority:** 🔴 CRITICAL |
| 47 | +**Effort:** ⚡ LOW (2-3 hours) |
| 48 | + |
| 49 | +**Issue:** |
| 50 | +The script recomputes project info multiple times (log header, JSON output) via `detect_project_info()` plus repeated `grep` parsing. Version text is also duplicated with mismatched values (header shows 1.0.59 but could drift from JSON output). |
| 51 | + |
| 52 | +**Impact:** |
| 53 | +- 🔴 **Version Drift:** Multiple version strings can get out of sync (critical for auditing) |
| 54 | +- 🔴 **Performance:** Redundant file scanning and grep operations |
| 55 | +- 🟡 **Maintenance:** Changes to metadata detection must be made in multiple places |
| 56 | + |
| 57 | +**Recommendation:** |
| 58 | +Extract project metadata helpers (`detect_project_info()`, `count_analyzed_files()`, `count_lines_of_code()`, `get_local_timestamp()`) into a dedicated helper file and return structured data once per run. Define a single `SCRIPT_VERSION` variable and reuse it in the banner, logs, and JSON payload to avoid drift. |
| 59 | + |
| 60 | +**Functions to Extract:** |
| 61 | +- `detect_project_info()` - ~100 lines (complex WordPress header parsing) |
| 62 | +- `count_analyzed_files()` - 3 lines |
| 63 | +- `count_lines_of_code()` - 19 lines |
| 64 | +- `get_local_timestamp()` - 3 lines |
| 65 | + |
| 66 | +**Version Consolidation:** |
| 67 | +- Create `SCRIPT_VERSION="1.0.59"` constant at top of script |
| 68 | +- Remove hardcoded version from line 4 header comment |
| 69 | +- Reuse `$SCRIPT_VERSION` in banner, logs, and JSON output |
| 70 | + |
| 71 | +**Total Lines to Move:** ~125 lines → `dist/bin/lib/project-helpers.sh` |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +### 3) Shared Check Runner for Contextual Grep Blocks |
| 76 | +**Severity:** 🟡 MEDIUM |
| 77 | +**Priority:** 🟡 MEDIUM |
| 78 | +**Effort:** ⚡⚡ HIGH (8-12 hours) |
| 79 | + |
| 80 | +**Issue:** |
| 81 | +After `run_check()` there are several bespoke blocks (admin capability checks, AJAX polling, REST pagination, etc.) that repeat the same pattern: `grep`, guard numeric line numbers, apply baseline suppression, build JSON findings, and manage per-check counters. Each block re-implements nearly identical loops and string parsing. |
| 82 | + |
| 83 | +**Impact:** |
| 84 | +- 🟡 **Code Duplication:** ~200+ lines of duplicated grep/baseline/counter logic |
| 85 | +- 🟡 **Bug Risk:** Fixes to baseline suppression must be applied to 6+ locations |
| 86 | +- 🟢 **Extensibility:** Adding new contextual checks requires copying entire pattern |
| 87 | + |
| 88 | +**Recommendation:** |
| 89 | +Extend `run_check()` (or add `run_context_check()`) to accept a callback/validator for contextual rules. Centralize baseline suppression, grouping, and JSON/check counter updates so individual rules only provide: patterns, optional context window, and the pass/fail message. This will collapse the duplicated state machines across those sections. |
| 90 | + |
| 91 | +**Affected Check Blocks:** |
| 92 | +1. Admin capability checks (lines ~1400-1500) |
| 93 | +2. AJAX polling detection (lines ~1500-1600) |
| 94 | +3. REST API pagination (lines ~1600-1700) |
| 95 | +4. Transient expiration checks (lines ~1700-1800) |
| 96 | +5. Direct database queries (lines ~1800-1900) |
| 97 | +6. Unescaped output (lines ~1900-2000) |
| 98 | + |
| 99 | +**Refactor Strategy:** |
| 100 | +- Create `run_context_check()` function that accepts: |
| 101 | + - Check name, severity, impact |
| 102 | + - Grep pattern(s) |
| 103 | + - Context validator callback (optional) |
| 104 | + - Message template |
| 105 | +- Centralize baseline suppression logic |
| 106 | +- Centralize JSON finding generation |
| 107 | +- Centralize counter management |
| 108 | + |
| 109 | +**Total Lines to Consolidate:** ~600 lines → ~150 lines (75% reduction) |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +### 4) Path + Timestamp Handling |
| 114 | +**Severity:** 🟢 LOW |
| 115 | +**Priority:** 🟢 LOW |
| 116 | +**Effort:** ⚡ TRIVIAL (30 minutes) |
| 117 | + |
| 118 | +**Issue:** |
| 119 | +`SCRIPT_DIR`/`PLUGIN_DIR` are computed twice, and timestamp helpers live inline despite similar helpers already existing in `dist/bin/lib/common-helpers.sh`. |
| 120 | + |
| 121 | +**Impact:** |
| 122 | +- 🟢 **Minor Duplication:** Only ~10 lines duplicated |
| 123 | +- 🟢 **Low Risk:** Path computation is stable and rarely changes |
| 124 | +- 🟢 **Consistency:** Should use shared helpers for uniformity |
| 125 | + |
| 126 | +**Recommendation:** |
| 127 | +Reuse the sourced helper values for directories and timestamps instead of reassigning them mid-file, ensuring a single place to update path or clock logic. |
| 128 | + |
| 129 | +**Current State:** |
| 130 | +- `get_local_timestamp()` defined inline (3 lines) - duplicates functionality |
| 131 | +- `timestamp_iso8601()` already exists in `common-helpers.sh` ✅ |
| 132 | +- `timestamp_filename()` already exists in `common-helpers.sh` ✅ |
| 133 | + |
| 134 | +**Action:** |
| 135 | +- Remove `get_local_timestamp()` function |
| 136 | +- Replace calls with `timestamp_iso8601()` or `timestamp_filename()` from common-helpers |
| 137 | +- Verify no duplicate `SCRIPT_DIR` assignments |
| 138 | + |
| 139 | +**Total Lines to Remove:** ~10 lines |
| 140 | + |
| 141 | +--- |
| 142 | + |
| 143 | +## 📊 Summary Matrix |
| 144 | + |
| 145 | +| Target | Severity | Priority | Effort | Lines Saved | Risk | |
| 146 | +|--------|----------|----------|--------|-------------|------| |
| 147 | +| **1. JSON + Reporting** | 🟡 MEDIUM | 🔵 HIGH | ⚡ MEDIUM (4-6h) | ~260 lines | Low - Well-isolated functions | |
| 148 | +| **2. Project Metadata** | 🔴 HIGH | 🔴 CRITICAL | ⚡ LOW (2-3h) | ~125 lines | Low - Pure data extraction | |
| 149 | +| **3. Context Check Runner** | 🟡 MEDIUM | 🟡 MEDIUM | ⚡⚡ HIGH (8-12h) | ~450 lines | Medium - Complex refactor | |
| 150 | +| **4. Path/Timestamp** | 🟢 LOW | 🟢 LOW | ⚡ TRIVIAL (30m) | ~10 lines | Very Low - Simple cleanup | |
| 151 | + |
| 152 | +**Total Potential Reduction:** ~845 lines (34% of 2,448-line script) |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +## 🎯 Recommended Implementation Order |
| 157 | + |
| 158 | +### Phase 1: Quick Wins (3-4 hours) |
| 159 | +1. ✅ **Target #4** - Path/Timestamp cleanup (30 min) |
| 160 | +2. ✅ **Target #2** - Project Metadata + Version SOT (2-3 hours) |
| 161 | + |
| 162 | +**Rationale:** Low risk, high impact on version drift bug, immediate value |
| 163 | + |
| 164 | +### Phase 2: Core Infrastructure (4-6 hours) |
| 165 | +3. ✅ **Target #1** - JSON + Reporting utilities (4-6 hours) |
| 166 | + |
| 167 | +**Rationale:** Enables future scripts to reuse JSON generation, prevents inconsistency |
| 168 | + |
| 169 | +### Phase 3: Advanced Refactor (8-12 hours) |
| 170 | +4. ✅ **Target #3** - Context Check Runner (8-12 hours) |
| 171 | + |
| 172 | +**Rationale:** Highest complexity, but biggest code reduction. Do last when other helpers are stable. |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +## ✅ Next Steps (Implementation Checklist) |
| 177 | + |
| 178 | +### Pre-Implementation |
| 179 | +- [ ] Create feature branch: `feature/dry-phase2-check-performance` |
| 180 | +- [ ] Backup current test baseline: `cp dist/tests/fixtures/.hcc-baseline .hcc-baseline.backup` |
| 181 | +- [ ] Run full test suite to establish baseline: `dist/tests/run-fixture-tests.sh` |
| 182 | + |
| 183 | +### Phase 1: Quick Wins |
| 184 | +- [ ] **Target #4:** Remove `get_local_timestamp()`, use `common-helpers.sh` functions |
| 185 | +- [ ] **Target #2:** Create `dist/bin/lib/project-helpers.sh` |
| 186 | + - [ ] Move `detect_project_info()`, `count_analyzed_files()`, `count_lines_of_code()` |
| 187 | + - [ ] Define `SCRIPT_VERSION` constant |
| 188 | + - [ ] Update all version references to use `$SCRIPT_VERSION` |
| 189 | +- [ ] Run tests: `dist/tests/run-fixture-tests.sh` (verify no regressions) |
| 190 | + |
| 191 | +### Phase 2: Core Infrastructure |
| 192 | +- [ ] **Target #1:** Create `dist/bin/lib/report-helpers.sh` |
| 193 | + - [ ] Move `json_escape()`, `url_encode()` |
| 194 | + - [ ] Move `add_json_finding()`, `add_json_check()` |
| 195 | + - [ ] Move `output_json()`, `generate_html_report()` |
| 196 | + - [ ] Source in `check-performance.sh` |
| 197 | +- [ ] Run tests: `dist/tests/run-fixture-tests.sh` (verify JSON output parity) |
| 198 | +- [ ] Test JSON output: `./dist/bin/check-performance.sh --paths dist/tests/fixtures/antipatterns.php --format json` |
| 199 | + |
| 200 | +### Phase 3: Advanced Refactor |
| 201 | +- [ ] **Target #3:** Create `run_context_check()` function |
| 202 | + - [ ] Design callback interface for context validators |
| 203 | + - [ ] Centralize baseline suppression logic |
| 204 | + - [ ] Centralize JSON finding generation |
| 205 | + - [ ] Refactor admin capability checks to use new runner |
| 206 | + - [ ] Refactor AJAX polling checks to use new runner |
| 207 | + - [ ] Refactor REST pagination checks to use new runner |
| 208 | + - [ ] Refactor remaining contextual checks |
| 209 | +- [ ] Run tests: `dist/tests/run-fixture-tests.sh` (verify all checks still work) |
| 210 | +- [ ] Compare output: `diff <(old_output) <(new_output)` (should be identical) |
| 211 | + |
| 212 | +### Post-Implementation |
| 213 | +- [ ] Update `CHANGELOG.md` with DRY Phase 2 completion |
| 214 | +- [ ] Update version to 1.0.60 |
| 215 | +- [ ] Run full test suite against real projects (Save Cart Later, etc.) |
| 216 | +- [ ] Document new helper functions with PHPDoc-style comments |
| 217 | +- [ ] Update `PROJECT-DRY-2.md` status to COMPLETE |
| 218 | +- [ ] Create `PROJECT-DRY-3.md` for next iteration (if needed) |
| 219 | + |
| 220 | +--- |
| 221 | + |
| 222 | +## 🚨 Risk Mitigation |
| 223 | + |
| 224 | +**Testing Strategy:** |
| 225 | +1. **Baseline Comparison:** Generate JSON output before/after each phase, compare with `diff` |
| 226 | +2. **Fixture Tests:** Run `dist/tests/run-fixture-tests.sh` after each change |
| 227 | +3. **Real-World Testing:** Test against known projects (Save Cart Later) before merging |
| 228 | +4. **Rollback Plan:** Keep feature branch until all tests pass |
| 229 | + |
| 230 | +**Known Risks:** |
| 231 | +- 🟡 **JSON Output Changes:** Any whitespace/formatting changes will break downstream tools |
| 232 | + - **Mitigation:** Use `jq` to normalize JSON before comparison |
| 233 | +- 🟡 **Baseline Suppression Logic:** Complex state machine, easy to break |
| 234 | + - **Mitigation:** Add unit tests for baseline matching before refactoring |
| 235 | +- 🟢 **Version Drift:** Low risk, easy to verify with grep |
| 236 | + |
| 237 | +--- |
| 238 | + |
| 239 | +**Status:** 📋 READY FOR IMPLEMENTATION |
| 240 | +**Estimated Total Effort:** 15-21 hours |
| 241 | +**Expected Code Reduction:** 34% (845 lines) |
| 242 | +**Next Action:** Create feature branch and start Phase 1 |
0 commit comments