Skip to content

Commit 4a4f35d

Browse files
authored
Merge pull request #102 from Hypercart-Dev-Tools/experimental/with-no-php
Add non PHP support to Development
2 parents d71be04 + 02fb381 commit 4a4f35d

File tree

2 files changed

+156
-19
lines changed

2 files changed

+156
-19
lines changed

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.2.1] - 2026-02-03
9+
10+
### Added
11+
12+
- **Enhanced project type detection for non-WordPress codebases:**
13+
- Detects frameworks from `package.json` dependencies: `nodejs`, `react`, `nextjs`, `vue`, `nuxt`, `express`, `typescript`
14+
- Comma-separated output when multiple frameworks detected (e.g., `[nodejs, react, nextjs]`)
15+
- Extracts project name, version, description, and author from `package.json`
16+
- Falls back to file-based detection: `javascript` (JS/TS files), `php` (PHP files), `php, javascript` (mixed)
17+
- WordPress plugin/theme detection unchanged and takes precedence
18+
19+
### Fixed
20+
21+
- **PHP superglobal rules now respect PHP-only scope in JS/Node scans:**
22+
- Updated `dist/bin/check-performance.sh` so Direct Superglobal Manipulation (`spo-002-superglobals`) and Unsanitized Superglobal Read rules explicitly restrict grep to `*.php` files.
23+
- Prevents PHP-specific security checks from scanning documentation files (e.g., `.md`) and non-PHP assets when running WPCC against JS/Node/React projects.
24+
- Resolves false positives where Markdown docs containing PHP examples triggered superglobal findings in JS-only repositories.
25+
26+
### Tested
27+
28+
- ✅ PHP file discovery and caching verified (lines 3147-3178)
29+
- ✅ PHP security patterns have `--include=*.php` guards (lines 3368, 3536)
30+
-`cached_grep` fallback handles JS-only projects (lines 3271-3280)
31+
-`unsanitized-superglobal-read.php` fixture: detects violations correctly
32+
-`wpdb-no-prepare.php` fixture: detects SQL injection risks
33+
- ✅ JS-only directory (`headless/`): runs without crashing, no false positives
34+
- ✅ Backwards compatibility confirmed: existing PHP scanning unchanged
35+
- ✅ Type detection: `nodejs` from package.json
36+
- ✅ Type detection: `nodejs, react, nextjs` from Next.js project
37+
- ✅ Type detection: `nodejs, typescript, vue, nuxt` from Nuxt project
38+
- ✅ Type detection: `php, javascript` from mixed fixtures directory
39+
40+
## [2.2.0] - 2026-02-03
41+
42+
### Added
43+
44+
- **JS/Node-only project support:** Relaxed the PHP file gate so WP Code Check can analyze pure JavaScript/TypeScript and Node/React codebases.
45+
- When no PHP files are found but JS/TS files are present, the scanner now skips PHP-only checks gracefully and runs headless/Node.js/JS pattern sets instead.
46+
- Grep helpers fall back to recursive search over the original paths when the PHP file cache is unavailable, preserving performance optimizations for PHP projects while enabling non-WordPress scans.
47+
- JSON and HTML report generation remain fully supported for these non-WordPress projects.
48+
849
## [2.1.0] - 2026-01-28
950

1051
### Added

dist/bin/check-performance.sh

Lines changed: 115 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bash
22
#
33
# WP Code Check by Hypercart - Performance Analysis Script
4-
# Version: 2.0.15
4+
# Version: 2.2.0
55
#
66
# Fast, zero-dependency WordPress performance analyzer
77
# Catches critical issues before they crash your site
@@ -81,7 +81,7 @@ source "$REPO_ROOT/lib/pattern-loader.sh"
8181
# This is the ONLY place the version number should be defined.
8282
# All other references (logs, JSON, banners) use this variable.
8383
# Update this ONE line when bumping versions - never hardcode elsewhere.
84-
SCRIPT_VERSION="2.1.0"
84+
SCRIPT_VERSION="2.2.1"
8585

8686
# Get the start/end line range for the enclosing function/method.
8787
#
@@ -1024,8 +1024,82 @@ detect_project_info() {
10241024
project_type="fixture"
10251025
project_name=$(basename "$scan_path")
10261026
else
1027-
# Generic project
1027+
# Generic project - detect from package.json or file types
10281028
project_name=$(basename "$scan_path")
1029+
1030+
# Check for package.json (Node.js/JS projects)
1031+
local pkg_json=""
1032+
if [ -f "$scan_path/package.json" ]; then
1033+
pkg_json="$scan_path/package.json"
1034+
elif [ -f "$(dirname "$scan_path")/package.json" ]; then
1035+
pkg_json="$(dirname "$scan_path")/package.json"
1036+
fi
1037+
1038+
if [ -n "$pkg_json" ]; then
1039+
# Build comma-separated type list from detected frameworks
1040+
local detected_types=""
1041+
1042+
# Base: it's a Node.js project
1043+
detected_types="nodejs"
1044+
1045+
# Detect TypeScript
1046+
if grep -qE '"typescript"|"ts-node"' "$pkg_json" 2>/dev/null; then
1047+
detected_types="$detected_types, typescript"
1048+
fi
1049+
1050+
# Detect React
1051+
if grep -qE '"react"[[:space:]]*:' "$pkg_json" 2>/dev/null; then
1052+
detected_types="$detected_types, react"
1053+
fi
1054+
1055+
# Detect Next.js (after React, as Next includes React)
1056+
if grep -qE '"next"[[:space:]]*:' "$pkg_json" 2>/dev/null; then
1057+
detected_types="$detected_types, nextjs"
1058+
fi
1059+
1060+
# Detect Vue.js
1061+
if grep -qE '"vue"[[:space:]]*:' "$pkg_json" 2>/dev/null; then
1062+
detected_types="$detected_types, vue"
1063+
fi
1064+
1065+
# Detect Nuxt (Vue's Next.js equivalent)
1066+
if grep -qE '"nuxt"[[:space:]]*:' "$pkg_json" 2>/dev/null; then
1067+
detected_types="$detected_types, nuxt"
1068+
fi
1069+
1070+
# Detect Express.js
1071+
if grep -qE '"express"[[:space:]]*:' "$pkg_json" 2>/dev/null; then
1072+
detected_types="$detected_types, express"
1073+
fi
1074+
1075+
project_type="$detected_types"
1076+
1077+
# Extract name/version from package.json if not already set
1078+
if [ "$project_name" = "Unknown" ] || [ -z "$project_name" ]; then
1079+
project_name=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4)
1080+
fi
1081+
if [ -z "$project_version" ]; then
1082+
project_version=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4)
1083+
fi
1084+
if [ -z "$project_description" ]; then
1085+
project_description=$(grep -o '"description"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4)
1086+
fi
1087+
if [ -z "$project_author" ]; then
1088+
project_author=$(grep -o '"author"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 | cut -d'"' -f4)
1089+
fi
1090+
elif [ -d "$scan_path" ]; then
1091+
# No package.json - detect by file presence
1092+
local has_js=$(find "$scan_path" -maxdepth 2 \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) -type f 2>/dev/null | head -1)
1093+
local has_php=$(find "$scan_path" -maxdepth 2 -name "*.php" -type f 2>/dev/null | head -1)
1094+
1095+
if [ -n "$has_js" ] && [ -n "$has_php" ]; then
1096+
project_type="php, javascript"
1097+
elif [ -n "$has_js" ]; then
1098+
project_type="javascript"
1099+
elif [ -n "$has_php" ]; then
1100+
project_type="php"
1101+
fi
1102+
fi
10291103
fi
10301104
fi
10311105

@@ -1194,6 +1268,8 @@ if [ "$ENABLE_LOGGING" = true ]; then
11941268
theme) type_display_log="WordPress Theme" ;;
11951269
fixture) type_display_log="Fixture Test" ;;
11961270
unknown) type_display_log="Unknown" ;;
1271+
# New types pass through as-is (already descriptive, e.g., "nodejs, react, nextjs")
1272+
nodejs*|javascript*|php*|react*|vue*|typescript*) type_display_log="$PROJECT_TYPE_LOG" ;;
11971273
esac
11981274
echo "Type: $type_display_log"
11991275
if [ -n "$PROJECT_AUTHOR_LOG" ]; then
@@ -1633,6 +1709,8 @@ generate_html_report() {
16331709
theme) type_display="WordPress Theme" ;;
16341710
fixture) type_display="Fixture Test" ;;
16351711
unknown) type_display="Unknown" ;;
1712+
# New types pass through as-is (already descriptive, e.g., "nodejs, react, nextjs")
1713+
nodejs*|javascript*|php*|react*|vue*|typescript*) type_display="$project_type" ;;
16361714
esac
16371715

16381716
project_info_html="<div style='font-size: 1.1em; font-weight: 600; margin-bottom: 5px;'>PROJECT INFORMATION</div>"
@@ -3023,11 +3101,18 @@ run_check() {
30233101
text_echo " PATHS: $PATHS"
30243102
fi
30253103

3026-
# PERFORMANCE: Use cached file list instead of grep -r
3027-
if [ "$PHP_FILE_COUNT" -eq 1 ]; then
3028-
result=$(grep -Hn $include_args $patterns "$PHP_FILE_LIST" 2>/dev/null) || true
3104+
# PERFORMANCE: Use cached file list instead of grep -r when available.
3105+
# When there are no PHP files (e.g., JS/Node-only projects), fall back
3106+
# to recursive grep over the original paths so JS/headless patterns
3107+
# still run.
3108+
if [ "$PHP_FILE_COUNT" -gt 0 ] && [ -n "$PHP_FILE_LIST" ] && [ -f "$PHP_FILE_LIST" ]; then
3109+
if [ "$PHP_FILE_COUNT" -eq 1 ]; then
3110+
result=$(grep -Hn $include_args $patterns "$PHP_FILE_LIST" 2>/dev/null) || true
3111+
else
3112+
result=$(cat "$PHP_FILE_LIST" | xargs grep -Hn $include_args $patterns 2>/dev/null) || true
3113+
fi
30293114
else
3030-
result=$(cat "$PHP_FILE_LIST" | xargs grep -Hn $include_args $patterns 2>/dev/null) || true
3115+
result=$(grep -rHn $EXCLUDE_ARGS $include_args $patterns "$PATHS" 2>/dev/null) || true
30313116
fi
30323117

30333118
if [ -n "$result" ]; then
@@ -3157,16 +3242,17 @@ else
31573242
PHP_FILE_COUNT=$(wc -l < "$PHP_FILE_LIST_CACHE" | tr -d ' ')
31583243

31593244
if [ "$PHP_FILE_COUNT" -eq 0 ]; then
3245+
# Relaxed PHP gate: it's valid to have JS/Node-only projects.
3246+
# We log for debugging but do not exit, so JS/Node/Headless checks can still run.
31603247
debug_echo "No PHP files found in: $PATHS"
31613248
rm -f "$PHP_FILE_LIST_CACHE"
3162-
echo "Error: No PHP files found in: $PATHS"
3163-
exit 1
3164-
fi
3165-
3166-
debug_echo "Cached $PHP_FILE_COUNT PHP files"
3249+
PHP_FILE_LIST=""
3250+
else
3251+
debug_echo "Cached $PHP_FILE_COUNT PHP files"
31673252

3168-
# Export for use in grep commands
3169-
PHP_FILE_LIST="$PHP_FILE_LIST_CACHE"
3253+
# Export for use in grep commands
3254+
PHP_FILE_LIST="$PHP_FILE_LIST_CACHE"
3255+
fi
31703256
fi
31713257

31723258
# Cleanup function to remove cache on exit
@@ -3256,13 +3342,19 @@ cached_grep() {
32563342
fi
32573343
done
32583344

3259-
# If single file mode, just use regular grep
3260-
if [ "$PHP_FILE_COUNT" -eq 1 ]; then
3345+
# If we have a cached PHP file list, use it; otherwise fall back to
3346+
# recursive grep on the original paths. This lets JS/Node-only repos
3347+
# (no PHP files) still be scanned safely without depending on the
3348+
# PHP_FILE_LIST cache.
3349+
if [ "$PHP_FILE_COUNT" -eq 1 ] && [ -n "$PHP_FILE_LIST" ] && [ -f "$PHP_FILE_LIST" ]; then
32613350
grep -Hn "${grep_args[@]}" "$pattern" "$PHP_FILE_LIST" 2>/dev/null || true
3262-
else
3351+
elif [ "$PHP_FILE_COUNT" -gt 1 ] && [ -n "$PHP_FILE_LIST" ] && [ -f "$PHP_FILE_LIST" ]; then
32633352
# Use cached file list with xargs for parallel processing
32643353
# -Hn adds filename and line number (like -rHn but without recursion)
32653354
cat "$PHP_FILE_LIST" | xargs grep -Hn "${grep_args[@]}" "$pattern" 2>/dev/null || true
3355+
else
3356+
# No PHP cache (e.g., JS-only project). Fall back to recursive grep.
3357+
grep -rHn "${grep_args[@]}" "$pattern" "$PATHS" 2>/dev/null || true
32663358
fi
32673359
}
32683360

@@ -3349,7 +3441,9 @@ SUPERGLOBAL_VISIBLE=""
33493441

33503442
# Find all superglobal manipulation patterns
33513443
# PERFORMANCE: Use cached file list instead of grep -r
3352-
SUPERGLOBAL_MATCHES=$(cached_grep -E "unset\\(\\$_(GET|POST|REQUEST|COOKIE)\\[|\\$_(GET|POST|REQUEST)[[:space:]]*=|\\$_(GET|POST|REQUEST|COOKIE)\\[[^]]*\\][[:space:]]*=" | \
3444+
# NOTE: Explicitly restrict to PHP files so that documentation (e.g. .md) and
3445+
# non-PHP assets are not scanned when running in JS-only or mixed repos.
3446+
SUPERGLOBAL_MATCHES=$(cached_grep --include=*.php -E "unset\\(\\$_(GET|POST|REQUEST|COOKIE)\\[|\\$_(GET|POST|REQUEST)[[:space:]]*=|\\$_(GET|POST|REQUEST|COOKIE)\\[[^]]*\\][[:space:]]*=" | \
33533447
grep -v '//.*\$_' || true)
33543448

33553449
if [ -n "$SUPERGLOBAL_MATCHES" ]; then
@@ -3515,7 +3609,9 @@ UNSANITIZED_VISIBLE=""
35153609
# Note: We do NOT exclude isset/empty here because they don't sanitize - they only check existence
35163610
# We'll filter those out in a more sophisticated way below
35173611
# PERFORMANCE: Use cached file list instead of grep -r
3518-
UNSANITIZED_MATCHES=$(cached_grep -E '\$_(GET|POST|REQUEST)\[' | \
3612+
# NOTE: Restrict to PHP files explicitly; in JS-only repos the fallback path in
3613+
# cached_grep will otherwise recurse into documentation and non-PHP assets.
3614+
UNSANITIZED_MATCHES=$(cached_grep --include=*.php -E '\$_(GET|POST|REQUEST)\[' | \
35193615
grep -v 'sanitize_' | \
35203616
grep -v 'esc_' | \
35213617
grep -v 'absint' | \

0 commit comments

Comments
 (0)