Files
noteflow/client/src-tauri/scripts/code_quality.sh

399 lines
12 KiB
Bash
Executable File

#!/bin/bash
# Rust/Tauri Code Quality Checks
# Detects: duplicate code, magic values, unused code, excessive complexity
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TAURI_SRC="${SCRIPT_DIR}/../src"
# Optional output file (tee stdout/stderr)
OUTPUT_FILE=""
while [[ $# -gt 0 ]]; do
case "$1" in
--output)
OUTPUT_FILE="${2:-}"
shift 2
;;
--output=*)
OUTPUT_FILE="${1#*=}"
shift
;;
*)
echo "Unknown argument: $1" >&2
exit 2
;;
esac
done
if [ -n "$OUTPUT_FILE" ]; then
mkdir -p "$(dirname "$OUTPUT_FILE")"
exec > >(tee "$OUTPUT_FILE") 2>&1
fi
# Colors for output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
ERRORS=0
WARNINGS=0
# Files to exclude from checks (auto-generated or test-only)
EXCLUDE_PATTERNS=(
"noteflow.rs" # Generated protobuf code
"proto_compliance_tests.rs" # Proto compliance test file
"*_pb.rs" # Any protobuf generated files
"*_generated.rs" # Any generated files
)
# Build find exclusion arguments
build_find_excludes() {
local excludes=""
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
excludes="$excludes ! -name '$pattern'"
done
echo "$excludes"
}
# Build grep exclusion arguments
build_grep_excludes() {
local excludes=""
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
excludes="$excludes --exclude=$pattern"
done
echo "$excludes"
}
FIND_EXCLUDES=$(build_find_excludes)
GREP_EXCLUDES=$(build_grep_excludes)
log_error() {
echo -e "${RED}ERROR:${NC} $1"
ERRORS=$((ERRORS + 1))
}
log_warning() {
echo -e "${YELLOW}WARNING:${NC} $1"
WARNINGS=$((WARNINGS + 1))
}
log_success() {
echo -e "${GREEN}OK:${NC} $1"
}
# Check if a file should be excluded
should_exclude() {
local file="$1"
local basename
basename=$(basename "$file")
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
if [[ "$basename" == $pattern ]]; then
return 0
fi
done
return 1
}
echo "=== Rust/Tauri Code Quality Checks ==="
echo ""
# Check 1: Magic numbers (excluding common ones like 0, 1, 2)
echo "Checking for magic numbers..."
MAGIC_NUMBERS=$(grep -rn --include="*.rs" $GREP_EXCLUDES -E '\b[0-9]{3,}\b' "$TAURI_SRC" \
| grep -v 'const ' \
| grep -v '// ' \
| grep -v '//!' \
| grep -v 'port' \
| grep -v 'timeout' \
| grep -v 'version' \
| grep -v '_test' \
| grep -v 'noteflow.rs' \
| grep -v 'assert' \
| grep -v '#\[cfg(test)\]' \
| grep -v 'mod tests' \
| grep -v 'from_millis' \
| grep -v 'from_secs' \
| grep -v 'Duration::' \
| grep -v 'channel' \
| grep -v 'clamp' \
| grep -v 'unwrap_or' \
| grep -v '0\.\.' \
| grep -v 'noise_floor' \
| grep -v 'items:' \
| grep -v 'samples_to_chunks' \
| grep -v 'for rate in' \
| grep -v 'progress:' \
| grep -v 'JobStatus::' \
| grep -v 'emit_progress' \
| head -20 || true)
if [ -n "$MAGIC_NUMBERS" ]; then
log_warning "Found potential magic numbers (consider using named constants):"
echo "$MAGIC_NUMBERS" | head -10
else
log_success "No obvious magic numbers found"
fi
echo ""
# Check 2: Hardcoded strings that should be constants
echo "Checking for repeated string literals..."
REPEATED_STRINGS=$(grep -roh --include="*.rs" $GREP_EXCLUDES '"[a-zA-Z_][a-zA-Z0-9_]{4,}"' "$TAURI_SRC" \
| sort | uniq -c | sort -rn \
| awk '$1 > 3 {print $0}' \
| head -10 || true)
if [ -n "$REPEATED_STRINGS" ]; then
log_warning "Found repeated string literals (consider extracting to constants):"
echo "$REPEATED_STRINGS"
else
log_success "No excessively repeated strings found"
fi
echo ""
# Check 3: TODO/FIXME comments
echo "Checking for TODO/FIXME comments..."
TODO_COUNT=$({ grep -rn --include="*.rs" $GREP_EXCLUDES -E '(TODO|FIXME|XXX|HACK):?' "$TAURI_SRC" 2>/dev/null || true; } | wc -l | xargs)
if [ "$TODO_COUNT" -gt 0 ]; then
log_warning "Found $TODO_COUNT TODO/FIXME comments"
grep -rn --include="*.rs" $GREP_EXCLUDES -E '(TODO|FIXME|XXX|HACK):?' "$TAURI_SRC" | head -10
else
log_success "No TODO/FIXME comments found"
fi
echo ""
# Check 4: Unused imports (using clippy)
echo "Checking for unused imports and dead code (clippy)..."
cd "${SCRIPT_DIR}/.."
if cargo clippy -- -W unused_imports -W dead_code 2>&1 | grep -E "warning:" | head -20; then
log_warning "Clippy found unused imports or dead code (see above)"
else
log_success "No unused imports or dead code detected"
fi
echo ""
# Check 5: Long functions (> 90 lines) - using proper brace counting
echo "Checking for long functions..."
LONG_FUNCTIONS=()
# Use awk for proper brace-depth tracking (portable for macOS and Linux)
count_function_lines() {
local file="$1"
awk '
BEGIN { in_fn = 0; fn_start = 0; fn_name = ""; brace_depth = 0 }
# Detect function start (pub/async/fn patterns)
/^[[:space:]]*(pub[[:space:]]+)?(async[[:space:]]+)?fn[[:space:]]+[a-zA-Z_]/ {
# If we were in a function, check its length
if (in_fn && (NR - fn_start) > 90) {
print FILENAME ":" fn_start " - " fn_name " (" (NR - fn_start) " lines)"
}
in_fn = 1
fn_start = NR
# Extract function name (portable: use gsub to isolate it)
fn_name = $0
gsub(/.*fn[[:space:]]+/, "", fn_name)
gsub(/[^a-zA-Z0-9_].*/, "", fn_name)
fn_name = "fn " fn_name
brace_depth = 0
}
# Count braces when in function
in_fn {
# Count opening braces
n = gsub(/{/, "{")
brace_depth += n
# Count closing braces
n = gsub(/}/, "}")
brace_depth -= n
# Function ends when brace depth returns to 0 after being > 0
if (brace_depth <= 0 && fn_start < NR) {
fn_lines = NR - fn_start + 1
if (fn_lines > 90) {
print FILENAME ":" fn_start " - " fn_name " (" fn_lines " lines)"
}
in_fn = 0
brace_depth = 0
}
}
' "$file"
}
while IFS= read -r -d '' file; do
if ! should_exclude "$file"; then
result=$(count_function_lines "$file")
if [ -n "$result" ]; then
while IFS= read -r line; do
LONG_FUNCTIONS+=("$line")
done <<< "$result"
fi
fi
done < <(find "$TAURI_SRC" -name "*.rs" -type f -print0)
if [ ${#LONG_FUNCTIONS[@]} -gt 0 ]; then
log_warning "Found ${#LONG_FUNCTIONS[@]} long functions (>90 lines):"
printf '%s\n' "${LONG_FUNCTIONS[@]}" | head -5
else
log_success "No excessively long functions found"
fi
echo ""
# Check 6: Deep nesting (> 7 levels = 28 spaces)
# Thresholds: 20=5 levels, 24=6 levels, 28=7 levels
# 7 levels allows for async patterns: spawn + block_on + loop + select + match + if + body
# Excludes: generated files, streaming (inherently deep), tracing macro field formatting
echo "Checking for deep nesting..."
DEEP_NESTING=$(grep -rn --include="*.rs" $GREP_EXCLUDES -E '^[[:space:]]{28,}[^[:space:]]' "$TAURI_SRC" \
| grep -v '//' \
| grep -v 'noteflow.rs' \
| grep -v 'streaming.rs' \
| grep -v 'tracing::' \
| grep -v ' = %' \
| grep -v ' = identity\.' \
| grep -v '[[:space:]]"[A-Z]' \
| head -20 || true)
if [ -n "$DEEP_NESTING" ]; then
log_warning "Found potentially deep nesting (>7 levels):"
echo "$DEEP_NESTING" | head -10
else
log_success "No excessively deep nesting found"
fi
echo ""
# Check 7: Unwrap usage (potential panics)
echo "Checking for unwrap() usage..."
UNWRAP_COUNT=$({ grep -rn --include="*.rs" $GREP_EXCLUDES '\.unwrap()' "$TAURI_SRC" 2>/dev/null | grep -v '_test' | grep -v '#\[test\]' || true; } | wc -l | xargs)
if [ "$UNWRAP_COUNT" -gt 0 ]; then
log_warning "Found $UNWRAP_COUNT unwrap() calls (use ? or expect())"
grep -rn --include="*.rs" $GREP_EXCLUDES '\.unwrap()' "$TAURI_SRC" | grep -v '_test' | head -10
else
log_success "No unwrap() calls found"
fi
echo ""
# Check 8: Clone abuse (with meticulous filtering for necessary clones)
echo "Checking for excessive clone() usage..."
CLONE_HEAVY_FILES=""
while IFS= read -r -d '' file; do
if ! should_exclude "$file"; then
# Total clone count
total=$(grep -c '\.clone()' "$file" 2>/dev/null || echo "0")
# Necessary clones: Arc::clone, state handles, app handles, channel senders,
# and other shared state patterns. These are idiomatic Rust patterns required
# for async/ownership semantics and cannot be avoided.
necessary=$(grep -cE '(Arc::clone|state\.clone|app_handle\.clone|handle\.clone|stream_manager\.clone|grpc_client\.clone|crypto\.clone|cancel_token\.clone|shutdown_flag\.clone|recording\.clone|client\.clone|audio_tx\.clone|capture_tx\.clone|inner\.clone|_tx\.clone|_rx\.clone|meeting_id\.clone|app\.clone|config\.clone)' "$file" 2>/dev/null || echo "0")
# Suspicious clones = total - necessary
if [[ "$total" =~ ^[0-9]+$ ]] && [[ "$necessary" =~ ^[0-9]+$ ]]; then
suspicious=$((total - necessary))
if [ "$suspicious" -gt 10 ]; then
CLONE_HEAVY_FILES="$CLONE_HEAVY_FILES$file:$suspicious suspicious (of $total total)
"
fi
fi
fi
done < <(find "$TAURI_SRC" -name "*.rs" -type f -print0)
if [ -n "$CLONE_HEAVY_FILES" ]; then
log_warning "Files with many suspicious clone() calls (>10):"
echo "$CLONE_HEAVY_FILES"
else
log_success "No excessive clone() usage detected"
fi
echo ""
# Check 9: Long parameter lists (> 5 params)
echo "Checking for functions with long parameter lists..."
LONG_PARAMS=$(grep -rn --include="*.rs" $GREP_EXCLUDES -E 'fn\s+\w+[^)]+,' "$TAURI_SRC" \
| grep -v 'noteflow.rs' \
| while read -r line; do
comma_count=$(echo "$line" | grep -o ',' | wc -l)
if [ "$comma_count" -gt 5 ]; then
echo "$line"
fi
done || true)
if [ -n "$LONG_PARAMS" ]; then
log_warning "Functions with >5 parameters:"
echo "$LONG_PARAMS" | head -5
else
log_success "No functions with excessive parameters found"
fi
echo ""
# Check 10: Duplicated error messages
echo "Checking for duplicated error messages..."
DUP_ERRORS=$(grep -roh --include="*.rs" $GREP_EXCLUDES 'Err\s*(\s*"[^"]*"' "$TAURI_SRC" \
| sort | uniq -c | sort -rn \
| awk '$1 > 2 {print $0}' || true)
if [ -n "$DUP_ERRORS" ]; then
log_warning "Found duplicated error messages (consider error enum):"
echo "$DUP_ERRORS" | head -5
else
log_success "No duplicated error messages found"
fi
echo ""
# Check 11: Module file size (excluding generated files)
echo "Checking module file sizes..."
LARGE_FILES=""
while IFS= read -r -d '' file; do
if ! should_exclude "$file"; then
lines=$(wc -l < "$file")
if [ "$lines" -gt 500 ]; then
LARGE_FILES="$LARGE_FILES $lines $file
"
fi
fi
done < <(find "$TAURI_SRC" -name "*.rs" -type f -print0)
if [ -n "$LARGE_FILES" ]; then
log_warning "Large files (>500 lines):"
echo "$LARGE_FILES" | sort -rn
else
log_success "All files within size limits"
fi
echo ""
# Check 12: Scattered helper patterns
echo "Checking for scattered helper functions..."
HELPER_FILES=""
while IFS= read -r -d '' file; do
if ! should_exclude "$file"; then
if grep -qE '^(pub\s+)?fn\s+(format_|parse_|convert_|to_|from_|is_|has_)' "$file" 2>/dev/null; then
HELPER_FILES="$HELPER_FILES$file
"
fi
fi
done < <(find "$TAURI_SRC" -name "*.rs" -type f -print0)
HELPER_COUNT=$(echo "$HELPER_FILES" | grep -c . || echo "0")
# Threshold of 8 allows for domain-specific helpers (e.g., audio/loader.rs, grpc/converters.rs)
# while still catching excessive scatter. Each module can reasonably have its own helpers.
if [ "$HELPER_COUNT" -gt 8 ]; then
log_warning "Helper functions scattered across $HELPER_COUNT files (consider consolidating):"
echo "$HELPER_FILES" | head -5
else
log_success "Helper functions reasonably centralized ($HELPER_COUNT files)"
fi
echo ""
# Summary
echo "=== Summary ==="
echo -e "Errors: ${RED}$ERRORS${NC}"
echo -e "Warnings: ${YELLOW}$WARNINGS${NC}"
if [ $ERRORS -gt 0 ] || [ $WARNINGS -gt 0 ]; then
exit 1
else
echo -e "${GREEN}Code quality checks passed!${NC}"
exit 0
fi