Files
gh-dhofheinz-open-plugins-p…/commands/documentation-validation/.scripts/example-validator.sh
2025-11-29 18:20:28 +08:00

326 lines
8.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================================
# Example Validator
# ============================================================================
# Purpose: Validate example quality and detect placeholder patterns
# Version: 1.0.0
# Usage: ./example-validator.sh <path> [--no-placeholders] [--recursive] [--json]
# Returns: 0=success, 1=warning, JSON output to stdout if --json
# ============================================================================
set -euo pipefail
# Default values
NO_PLACEHOLDERS=true
RECURSIVE=true
JSON_OUTPUT=false
EXTENSIONS="md,txt,json,sh,py,js,ts,yaml,yml"
# Parse arguments
TARGET_PATH="${1:-.}"
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--no-placeholders)
NO_PLACEHOLDERS=true
shift
;;
--allow-placeholders)
NO_PLACEHOLDERS=false
shift
;;
--recursive)
RECURSIVE=true
shift
;;
--non-recursive)
RECURSIVE=false
shift
;;
--json)
JSON_OUTPUT=true
shift
;;
--extensions)
EXTENSIONS="$2"
shift 2
;;
*)
shift
;;
esac
done
# Initialize counters
files_checked=0
example_count=0
placeholder_count=0
todo_count=0
declare -a issues=()
declare -a files_with_issues=()
# Build find command based on recursiveness
if $RECURSIVE; then
FIND_DEPTH=""
else
FIND_DEPTH="-maxdepth 1"
fi
# Build extension pattern
ext_pattern=""
IFS=',' read -ra EXT_ARRAY <<< "$EXTENSIONS"
for ext in "${EXT_ARRAY[@]}"; do
if [[ -z "$ext_pattern" ]]; then
ext_pattern="-name '*.${ext}'"
else
ext_pattern="$ext_pattern -o -name '*.${ext}'"
fi
done
# Find files to check
mapfile -t files < <(eval "find '$TARGET_PATH' $FIND_DEPTH -type f \( $ext_pattern \) 2>/dev/null" || true)
# Placeholder patterns to detect
declare -a PLACEHOLDER_PATTERNS=(
'TODO[:\)]'
'FIXME[:\)]'
'XXX[:\)]'
'HACK[:\)]'
'placeholder'
'PLACEHOLDER'
'your-.*-here'
'<your-'
'INSERT.?HERE'
'YOUR_[A-Z_]+'
)
# Generic dummy value patterns
declare -a GENERIC_PATTERNS=(
'\bfoo\b'
'\bbar\b'
'\bbaz\b'
'\bdummy\b'
)
# Acceptable patterns (don't count these)
declare -a ACCEPTABLE_PATTERNS=(
'\{\{[^}]+\}\}' # {{variable}} template syntax
'\$\{[^}]+\}' # ${variable} template syntax
'\$[A-Z_]+' # $VARIABLE environment variables
)
# Function to check if line contains acceptable pattern
is_acceptable_pattern() {
local line="$1"
for pattern in "${ACCEPTABLE_PATTERNS[@]}"; do
if echo "$line" | grep -qE "$pattern"; then
return 0 # Is acceptable
fi
done
return 1 # Not acceptable
}
# Function to count code examples in markdown
count_code_examples() {
local file="$1"
if [[ ! "$file" =~ \.md$ ]]; then
return 0
fi
# Count code blocks (```)
local count
count=$(grep -c '```' "$file" 2>/dev/null || echo "0")
# Ensure count is numeric and divide by 2 since each code block has opening and closing
if [[ "$count" =~ ^[0-9]+$ ]]; then
count=$((count / 2))
else
count=0
fi
echo "$count"
}
# Check each file
for file in "${files[@]}"; do
((files_checked++)) || true
# Count examples in markdown files
if [[ "$file" =~ \.md$ ]]; then
file_examples=$(count_code_examples "$file")
((example_count += file_examples)) || true
fi
file_issues=0
# Check for placeholder patterns
for pattern in "${PLACEHOLDER_PATTERNS[@]}"; do
while IFS=: read -r line_num line_content; do
# Skip if it's an acceptable pattern
if is_acceptable_pattern "$line_content"; then
continue
fi
((placeholder_count++)) || true
((file_issues++)) || true
issue="$file:$line_num: Placeholder pattern detected"
issues+=("$issue")
# Track TODO/FIXME separately
if echo "$pattern" | grep -qE 'TODO|FIXME|XXX'; then
((todo_count++)) || true
fi
done < <(grep -inE "$pattern" "$file" 2>/dev/null || true)
done
# Check for generic dummy values (only in non-test files)
if [[ ! "$file" =~ test ]] && [[ ! "$file" =~ example ]] && [[ ! "$file" =~ spec ]]; then
for pattern in "${GENERIC_PATTERNS[@]}"; do
while IFS=: read -r line_num line_content; do
# Skip code comments explaining these terms
if echo "$line_content" | grep -qE '(#|//|/\*).*'"$pattern"; then
continue
fi
# Skip if in an acceptable context
if is_acceptable_pattern "$line_content"; then
continue
fi
((file_issues++)) || true
issue="$file:$line_num: Generic placeholder value detected"
issues+=("$issue")
done < <(grep -inE "$pattern" "$file" 2>/dev/null || true)
done
fi
# Track files with issues
if ((file_issues > 0)); then
files_with_issues+=("$file:$file_issues")
fi
done
# Calculate quality score
quality_score=100
((quality_score -= placeholder_count * 10)) || true
((quality_score -= todo_count * 5)) || true
if ((example_count < 2)); then
((quality_score -= 20)) || true
fi
# Ensure score doesn't go negative
if ((quality_score < 0)); then
quality_score=0
fi
# Determine status
status="pass"
if ((quality_score < 60)); then
status="fail"
elif ((quality_score < 80)); then
status="warning"
fi
# Output results
if $JSON_OUTPUT; then
# Build JSON output
cat <<EOF
{
"files_checked": $files_checked,
"example_count": $example_count,
"placeholder_count": $placeholder_count,
"todo_count": $todo_count,
"files_with_issues": ${#files_with_issues[@]},
"quality_score": $quality_score,
"status": "$status",
"issues": [
$(IFS=; for issue in "${issues[@]:0:20}"; do # Limit to first 20 issues
# Escape quotes in issue text
escaped_issue="${issue//\"/\\\"}"
echo " \"$escaped_issue\","
done | sed '$ s/,$//')
],
"files_with_issues_list": [
$(IFS=; for file_info in "${files_with_issues[@]:0:10}"; do # Limit to first 10 files
file_path="${file_info%:*}"
file_count="${file_info#*:}"
echo " {\"file\": \"$file_path\", \"issue_count\": $file_count},"
done | sed '$ s/,$//')
]
}
EOF
else
# Human-readable output
echo ""
echo "Example Quality Validation"
echo "========================================"
echo "Files Checked: $files_checked"
echo "Code Examples Found: $example_count"
echo "Quality Score: $quality_score/100"
echo ""
if ((placeholder_count > 0)) || ((todo_count > 0)); then
echo "Issues Detected:"
echo " • Placeholder patterns: $placeholder_count"
echo " • TODO/FIXME markers: $todo_count"
echo " • Files with issues: ${#files_with_issues[@]}"
echo ""
if ((${#files_with_issues[@]} > 0)); then
echo "Files with issues:"
for file_info in "${files_with_issues[@]:0:5}"; do # Show first 5
file_path="${file_info%:*}"
file_count="${file_info#*:}"
echo "$file_path ($file_count issues)"
done
if ((${#files_with_issues[@]} > 5)); then
echo " ... and $((${#files_with_issues[@]} - 5)) more files"
fi
fi
echo ""
echo "Sample Issues:"
for issue in "${issues[@]:0:5}"; do # Show first 5
echo "$issue"
done
if ((${#issues[@]} > 5)); then
echo " ... and $((${#issues[@]} - 5)) more issues"
fi
else
echo "✓ No placeholder patterns detected"
fi
if ((example_count < 2)); then
echo ""
echo "⚠ Recommendation: Add more code examples (found: $example_count, recommended: 3+)"
fi
echo ""
if [[ "$status" == "pass" ]]; then
echo "Overall: ✓ PASS"
elif [[ "$status" == "warning" ]]; then
echo "Overall: ⚠ WARNINGS"
else
echo "Overall: ✗ FAIL"
fi
echo ""
fi
# Exit with appropriate code
if [[ "$status" == "fail" ]]; then
exit 1
elif [[ "$status" == "warning" ]]; then
exit 0 # Warning is not a failure
else
exit 0
fi