Files
gh-dhofheinz-open-plugins-p…/commands/atomic-commit/.scripts/dependency-checker.sh
2025-11-29 18:20:25 +08:00

329 lines
8.5 KiB
Bash
Executable File

#!/bin/bash
# Dependency Checker - Analyze file dependencies
#
# Purpose: Check for dependencies between files to inform commit ordering
# Version: 1.0.0
# Usage: ./dependency-checker.sh [files...]
# If no files specified, analyzes all changed files
# Returns:
# Exit 0: Success
# Exit 1: Error
# Exit 2: Invalid parameters
#
# Dependencies: git, bash 4.0+, grep
set -euo pipefail
VERBOSE=false
OUTPUT_FORMAT="text"
# Parse options
while [[ $# -gt 0 ]]; do
case "$1" in
--verbose)
VERBOSE=true
shift
;;
--format)
OUTPUT_FORMAT="${2:-text}"
shift 2
;;
*)
break
;;
esac
done
# Logging
log() {
if [[ "$VERBOSE" == "true" ]]; then
echo "[DEBUG] $*" >&2
fi
}
# Get changed files if not specified
FILES=()
if [[ $# -eq 0 ]]; then
log "Getting changed files from git..."
while IFS= read -r file; do
[[ -n "$file" ]] && FILES+=("$file")
done < <(git diff --cached --name-only; git diff --name-only)
else
FILES=("$@")
fi
# Remove duplicates
mapfile -t FILES < <(printf '%s\n' "${FILES[@]}" | sort -u)
if [[ ${#FILES[@]} -eq 0 ]]; then
echo "No files to analyze" >&2
exit 1
fi
log "Analyzing ${#FILES[@]} files for dependencies..."
# Dependency storage
declare -A dependencies
declare -A file_types
# Detect file type
detect_file_type() {
local file="$1"
case "$file" in
*.py)
echo "python"
;;
*.js|*.jsx)
echo "javascript"
;;
*.ts|*.tsx)
echo "typescript"
;;
*.go)
echo "go"
;;
*.java)
echo "java"
;;
*.rb)
echo "ruby"
;;
*.rs)
echo "rust"
;;
*.md|*.txt|*.rst)
echo "docs"
;;
*test*|*spec*)
echo "test"
;;
*)
echo "unknown"
;;
esac
}
# Extract imports from Python file
extract_python_imports() {
local file="$1"
local content
if [[ -f "$file" ]]; then
content=$(cat "$file")
else
# Try to get from git
content=$(git show :"$file" 2>/dev/null || echo "")
fi
# Match: import module, from module import
echo "$content" | grep -E "^(import|from) " | sed -E 's/^(import|from) ([a-zA-Z0-9_.]+).*/\2/' || true
}
# Extract imports from JavaScript/TypeScript
extract_js_imports() {
local file="$1"
local content
if [[ -f "$file" ]]; then
content=$(cat "$file")
else
content=$(git show :"$file" 2>/dev/null || echo "")
fi
# Match: import from, require()
{
echo "$content" | grep -oE "import .* from ['\"]([^'\"]+)['\"]" | sed -E "s/.*from ['\"](.*)['\"].*/\1/" || true
echo "$content" | grep -oE "require\(['\"]([^'\"]+)['\"]\)" | sed -E "s/.*require\(['\"]([^'\"]+)['\"]\).*/\1/" || true
} | sort -u
}
# Extract imports from Go
extract_go_imports() {
local file="$1"
local content
if [[ -f "$file" ]]; then
content=$(cat "$file")
else
content=$(git show :"$file" 2>/dev/null || echo "")
fi
# Match: import "module" or import ( "module" )
echo "$content" | grep -oE 'import +"[^"]+"' | sed -E 's/import +"([^"]+)".*/\1/' || true
echo "$content" | sed -n '/^import (/,/^)/p' | grep -oE '"[^"]+"' | tr -d '"' || true
}
# Convert import path to file path
import_to_file() {
local import_path="$1"
local file_type="$2"
case "$file_type" in
python)
# module.submodule -> module/submodule.py
echo "$import_path" | tr '.' '/' | sed 's|$|.py|'
;;
javascript|typescript)
# Handle relative imports
if [[ "$import_path" == ./* ]] || [[ "$import_path" == ../* ]]; then
echo "$import_path"
else
# node_modules - not a local file
echo ""
fi
;;
go)
# Package imports - check if local
if [[ "$import_path" == github.com/* ]]; then
echo ""
else
echo "$import_path"
fi
;;
*)
echo ""
;;
esac
}
# Find dependencies for each file
log "Extracting imports and dependencies..."
for file in "${FILES[@]}"; do
file_type=$(detect_file_type "$file")
file_types["$file"]="$file_type"
log " $file: type=$file_type"
case "$file_type" in
python)
imports=$(extract_python_imports "$file")
;;
javascript|typescript)
imports=$(extract_js_imports "$file")
;;
go)
imports=$(extract_go_imports "$file")
;;
*)
imports=""
;;
esac
if [[ -n "$imports" ]]; then
log " Imports:"
while IFS= read -r import_path; do
[[ -z "$import_path" ]] && continue
log " - $import_path"
# Convert import to file path
imported_file=$(import_to_file "$import_path" "$file_type")
# Check if imported file is in our file list
if [[ -n "$imported_file" ]]; then
for other_file in "${FILES[@]}"; do
if [[ "$other_file" == *"$imported_file"* ]]; then
# file depends on other_file
if [[ -z "${dependencies[$file]:-}" ]]; then
dependencies["$file"]="$other_file"
else
dependencies["$file"]="${dependencies[$file]},$other_file"
fi
log " Dependency: $file -> $other_file"
fi
done
fi
done <<< "$imports"
fi
done
# Detect test dependencies
log "Detecting test dependencies..."
for file in "${FILES[@]}"; do
if [[ "${file_types[$file]}" == "test" ]]; then
# Test file depends on implementation file
impl_file="${file//test/}"
impl_file="${impl_file//.test/}"
impl_file="${impl_file//.spec/}"
impl_file="${impl_file//tests\//}"
impl_file="${impl_file//spec\//}"
for other_file in "${FILES[@]}"; do
if [[ "$other_file" == *"$impl_file"* ]] && [[ "$other_file" != "$file" ]]; then
if [[ -z "${dependencies[$file]:-}" ]]; then
dependencies["$file"]="$other_file"
else
dependencies["$file"]="${dependencies[$file]},$other_file"
fi
log " Test dependency: $file -> $other_file"
fi
done
fi
done
# Output results
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
echo "{"
echo " \"files\": ["
local first=true
for file in "${FILES[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
echo ","
fi
echo -n " {\"file\": \"$file\", \"type\": \"${file_types[$file]}\""
if [[ -n "${dependencies[$file]:-}" ]]; then
IFS=',' read -ra deps <<< "${dependencies[$file]}"
echo -n ", \"depends_on\": ["
local first_dep=true
for dep in "${deps[@]}"; do
if [[ "$first_dep" == "true" ]]; then
first_dep=false
else
echo -n ", "
fi
echo -n "\"$dep\""
done
echo -n "]"
fi
echo -n "}"
done
echo ""
echo " ]"
echo "}"
else
echo "=== FILE DEPENDENCIES ==="
echo ""
echo "Files analyzed: ${#FILES[@]}"
echo ""
local has_dependencies=false
for file in "${FILES[@]}"; do
if [[ -n "${dependencies[$file]:-}" ]]; then
has_dependencies=true
echo "File: $file"
echo " Type: ${file_types[$file]}"
echo " Depends on:"
IFS=',' read -ra deps <<< "${dependencies[$file]}"
for dep in "${deps[@]}"; do
echo " - $dep"
done
echo ""
fi
done
if [[ "$has_dependencies" == "false" ]]; then
echo "No dependencies detected."
echo "All files can be committed independently."
else
echo "Recommendation:"
echo " Commit dependencies before dependent files."
echo " Group dependent files in same commit if they form atomic unit."
fi
fi
log "Dependency analysis complete"
exit 0