사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)
This commit is contained in:
298
analyze_unused_files.py
Normal file
298
analyze_unused_files.py
Normal file
@@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Set, Dict, List, Tuple
|
||||
import json
|
||||
|
||||
class DartFileAnalyzer:
|
||||
def __init__(self, project_root: str):
|
||||
self.project_root = Path(project_root)
|
||||
self.lib_path = self.project_root / "lib"
|
||||
self.all_files: Set[Path] = set()
|
||||
self.used_files: Set[Path] = set()
|
||||
self.import_graph: Dict[Path, Set[Path]] = {}
|
||||
|
||||
# 진입점들
|
||||
self.entry_points = [
|
||||
self.lib_path / "main.dart",
|
||||
self.lib_path / "injection_container.dart",
|
||||
self.lib_path / "screens/common/app_layout.dart"
|
||||
]
|
||||
|
||||
def find_all_dart_files(self) -> None:
|
||||
"""모든 .dart 파일을 찾기 (생성 파일 제외)"""
|
||||
for file_path in self.lib_path.rglob("*.dart"):
|
||||
if not (file_path.name.endswith(".g.dart") or file_path.name.endswith(".freezed.dart")):
|
||||
self.all_files.add(file_path)
|
||||
self.import_graph[file_path] = set()
|
||||
|
||||
def extract_imports(self, file_path: Path) -> Set[Path]:
|
||||
"""파일에서 import 문을 추출하고 실제 파일 경로로 변환"""
|
||||
imports = set()
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# import 패턴 매칭
|
||||
import_patterns = [
|
||||
r"import\s+['\"]package:superport/(.+?)['\"]",
|
||||
r"import\s+['\"](.+?\.dart)['\"]"
|
||||
]
|
||||
|
||||
for pattern in import_patterns:
|
||||
matches = re.findall(pattern, content)
|
||||
for match in matches:
|
||||
if match.startswith('package:superport/'):
|
||||
# package:superport/ import
|
||||
relative_path = match.replace('package:superport/', '')
|
||||
imported_file = self.lib_path / relative_path
|
||||
else:
|
||||
# 상대 경로 import
|
||||
if match.startswith('../') or match.startswith('./'):
|
||||
imported_file = file_path.parent / match
|
||||
imported_file = imported_file.resolve()
|
||||
else:
|
||||
imported_file = file_path.parent / match
|
||||
|
||||
if imported_file.exists() and imported_file.suffix == '.dart':
|
||||
imports.add(imported_file)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read {file_path}: {e}")
|
||||
|
||||
return imports
|
||||
|
||||
def build_import_graph(self) -> None:
|
||||
"""모든 파일의 import 관계를 분석"""
|
||||
print("Building import graph...")
|
||||
for file_path in self.all_files:
|
||||
self.import_graph[file_path] = self.extract_imports(file_path)
|
||||
|
||||
def trace_used_files(self, start_file: Path, visited: Set[Path] = None) -> None:
|
||||
"""진입점에서 시작해서 사용되는 모든 파일을 추적"""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
|
||||
if start_file in visited or start_file not in self.all_files:
|
||||
return
|
||||
|
||||
visited.add(start_file)
|
||||
self.used_files.add(start_file)
|
||||
|
||||
# 이 파일에서 import하는 모든 파일들을 재귀적으로 추적
|
||||
for imported_file in self.import_graph.get(start_file, set()):
|
||||
self.trace_used_files(imported_file, visited)
|
||||
|
||||
def find_unused_files(self) -> Set[Path]:
|
||||
"""사용되지 않는 파일들 찾기"""
|
||||
print("Tracing used files from entry points...")
|
||||
|
||||
# 각 진입점에서 시작해서 사용되는 파일들을 추적
|
||||
for entry_point in self.entry_points:
|
||||
if entry_point.exists():
|
||||
print(f"Tracing from {entry_point.relative_to(self.project_root)}")
|
||||
self.trace_used_files(entry_point)
|
||||
else:
|
||||
print(f"Warning: Entry point {entry_point} does not exist")
|
||||
|
||||
# test 파일들도 별도 진입점으로 처리 (필요시)
|
||||
test_files = list(self.project_root.glob("test/**/*.dart"))
|
||||
for test_file in test_files:
|
||||
if test_file.suffix == '.dart' and not (test_file.name.endswith('.g.dart') or test_file.name.endswith('.freezed.dart')):
|
||||
print(f"Tracing from test file: {test_file.relative_to(self.project_root)}")
|
||||
self.trace_used_files(test_file)
|
||||
|
||||
unused = self.all_files - self.used_files
|
||||
return unused
|
||||
|
||||
def categorize_unused_files(self, unused_files: Set[Path]) -> Dict[str, List[Path]]:
|
||||
"""사용되지 않는 파일들을 카테고리별로 분류"""
|
||||
categories = {
|
||||
'generated_files': [],
|
||||
'models': [],
|
||||
'screens': [],
|
||||
'services': [],
|
||||
'repositories': [],
|
||||
'controllers': [],
|
||||
'widgets': [],
|
||||
'utils': [],
|
||||
'others': []
|
||||
}
|
||||
|
||||
for file_path in unused_files:
|
||||
relative_path = file_path.relative_to(self.lib_path)
|
||||
path_str = str(relative_path)
|
||||
|
||||
if file_path.name.endswith('.g.dart') or file_path.name.endswith('.freezed.dart'):
|
||||
categories['generated_files'].append(file_path)
|
||||
elif 'models/' in path_str:
|
||||
categories['models'].append(file_path)
|
||||
elif 'screens/' in path_str:
|
||||
categories['screens'].append(file_path)
|
||||
elif 'services/' in path_str:
|
||||
categories['services'].append(file_path)
|
||||
elif 'repositories/' in path_str:
|
||||
categories['repositories'].append(file_path)
|
||||
elif 'controller' in path_str:
|
||||
categories['controllers'].append(file_path)
|
||||
elif 'widgets/' in path_str:
|
||||
categories['widgets'].append(file_path)
|
||||
elif 'utils/' in path_str:
|
||||
categories['utils'].append(file_path)
|
||||
else:
|
||||
categories['others'].append(file_path)
|
||||
|
||||
return categories
|
||||
|
||||
def check_safety_for_deletion(self, file_path: Path) -> Tuple[bool, str]:
|
||||
"""파일 삭제의 안전성 검사"""
|
||||
relative_path = file_path.relative_to(self.lib_path)
|
||||
path_str = str(relative_path)
|
||||
|
||||
# 안전하게 삭제 가능한 조건들
|
||||
if file_path.name.endswith('.g.dart') or file_path.name.endswith('.freezed.dart'):
|
||||
return True, "자동 생성 파일 - 소스 파일 삭제 후 재생성 가능"
|
||||
|
||||
# 위험할 수 있는 파일들
|
||||
critical_keywords = ['main.dart', 'injection', 'app_layout', 'core/', 'constants/']
|
||||
if any(keyword in path_str for keyword in critical_keywords):
|
||||
return False, "핵심 시스템 파일 - 삭제 전 추가 검토 필요"
|
||||
|
||||
# pubspec.yaml에서 참조되는지 확인 필요
|
||||
if 'assets/' in path_str or 'fonts/' in path_str:
|
||||
return False, "에셋 파일 - pubspec.yaml 참조 확인 필요"
|
||||
|
||||
return True, "안전하게 삭제 가능"
|
||||
|
||||
def generate_report(self) -> None:
|
||||
"""분석 결과 보고서 생성"""
|
||||
print("\n" + "="*80)
|
||||
print("FLUTTER 프로젝트 사용되지 않는 파일 분석 보고서")
|
||||
print("="*80)
|
||||
|
||||
unused_files = self.find_unused_files()
|
||||
categories = self.categorize_unused_files(unused_files)
|
||||
|
||||
print(f"\n📊 총 분석 파일: {len(self.all_files)}개")
|
||||
print(f"✅ 사용 중인 파일: {len(self.used_files)}개")
|
||||
print(f"❌ 사용되지 않는 파일: {len(unused_files)}개")
|
||||
print(f"📈 사용률: {len(self.used_files)/len(self.all_files)*100:.1f}%")
|
||||
|
||||
# 진입점 정보
|
||||
print(f"\n🚀 분석 진입점:")
|
||||
for entry_point in self.entry_points:
|
||||
status = "✅" if entry_point.exists() else "❌"
|
||||
print(f" {status} {entry_point.relative_to(self.project_root)}")
|
||||
|
||||
# 카테고리별 미사용 파일
|
||||
print(f"\n📂 카테고리별 사용되지 않는 파일:")
|
||||
total_safe_to_delete = 0
|
||||
total_need_review = 0
|
||||
|
||||
for category, files in categories.items():
|
||||
if not files:
|
||||
continue
|
||||
|
||||
print(f"\n📁 {category.upper()} ({len(files)}개):")
|
||||
|
||||
safe_count = 0
|
||||
review_count = 0
|
||||
|
||||
for file_path in sorted(files):
|
||||
relative_path = file_path.relative_to(self.lib_path)
|
||||
is_safe, reason = self.check_safety_for_deletion(file_path)
|
||||
|
||||
if is_safe:
|
||||
safe_count += 1
|
||||
print(f" ✅ lib/{relative_path}")
|
||||
else:
|
||||
review_count += 1
|
||||
print(f" ⚠️ lib/{relative_path} - {reason}")
|
||||
|
||||
print(f" 안전 삭제: {safe_count}개, 검토 필요: {review_count}개")
|
||||
total_safe_to_delete += safe_count
|
||||
total_need_review += review_count
|
||||
|
||||
# 삭제 권장사항
|
||||
print(f"\n🎯 삭제 권장사항:")
|
||||
print(f" ✅ 즉시 안전 삭제 가능: {total_safe_to_delete}개")
|
||||
print(f" ⚠️ 추가 검토 후 삭제: {total_need_review}개")
|
||||
|
||||
# Git status와 비교
|
||||
print(f"\n📋 Git Status와 비교 분석:")
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(['git', 'status', '--porcelain'],
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True)
|
||||
git_status = result.stdout
|
||||
|
||||
deleted_files = []
|
||||
for line in git_status.split('\n'):
|
||||
if line.startswith('D ') and line.endswith('.dart'):
|
||||
deleted_files.append(line[3:]) # Remove 'D ' prefix
|
||||
|
||||
if deleted_files:
|
||||
print(f" 🗑️ Git에서 이미 삭제된 파일: {len(deleted_files)}개")
|
||||
for deleted_file in deleted_files[:10]: # Show first 10
|
||||
print(f" - {deleted_file}")
|
||||
if len(deleted_files) > 10:
|
||||
print(f" ... 및 {len(deleted_files) - 10}개 더")
|
||||
else:
|
||||
print(f" ✅ Git에서 삭제된 dart 파일 없음")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Git status 확인 실패: {e}")
|
||||
|
||||
# 중요한 패턴 분석
|
||||
print(f"\n🔍 중요 패턴 분석:")
|
||||
|
||||
# .g.dart / .freezed.dart 파일들의 소스 파일 존재 여부
|
||||
generated_files = categories.get('generated_files', [])
|
||||
if generated_files:
|
||||
orphaned_generated = []
|
||||
for gen_file in generated_files:
|
||||
if gen_file.name.endswith('.g.dart'):
|
||||
source_file = gen_file.with_suffix('').with_suffix('.dart')
|
||||
elif gen_file.name.endswith('.freezed.dart'):
|
||||
source_file = gen_file.parent / gen_file.name.replace('.freezed.dart', '.dart')
|
||||
else:
|
||||
continue
|
||||
|
||||
if source_file not in self.all_files and not source_file.exists():
|
||||
orphaned_generated.append(gen_file)
|
||||
|
||||
if orphaned_generated:
|
||||
print(f" ⚠️ 소스 파일이 없는 생성 파일: {len(orphaned_generated)}개")
|
||||
for orphaned in orphaned_generated[:5]:
|
||||
print(f" - lib/{orphaned.relative_to(self.lib_path)}")
|
||||
else:
|
||||
print(f" ✅ 모든 생성 파일의 소스 파일 존재함")
|
||||
|
||||
print(f"\n💡 권장사항:")
|
||||
print(f" 1. 자동 생성 파일들을 먼저 정리하세요")
|
||||
print(f" 2. 테스트 파일에서만 사용되는 파일들을 확인하세요")
|
||||
print(f" 3. 삭제 전에 git grep으로 문자열 참조 확인을 권장합니다")
|
||||
print(f" 4. 백업을 만든 후 단계적으로 삭제하세요")
|
||||
|
||||
print("\n" + "="*80)
|
||||
|
||||
def main():
|
||||
project_root = "/Users/maximilian.j.sul/Documents/flutter/superport"
|
||||
analyzer = DartFileAnalyzer(project_root)
|
||||
|
||||
print("🔍 Flutter 프로젝트 사용되지 않는 파일 분석 시작...")
|
||||
print(f"📂 프로젝트 경로: {project_root}")
|
||||
|
||||
analyzer.find_all_dart_files()
|
||||
print(f"📄 발견된 총 Dart 파일: {len(analyzer.all_files)}개")
|
||||
|
||||
analyzer.build_import_graph()
|
||||
analyzer.generate_report()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user