"""Framework detector for identifying web frameworks and libraries.""" import re from pathlib import Path from typing import List from src.data.patterns import FRAMEWORK_INDICATOR_FILES, FRAMEWORK_MARKERS from src.detectors.base import BaseDetector class FrameworkDetector(BaseDetector): """Detects frameworks used in a project.""" def __init__(self) -> None: self.indicator_files = FRAMEWORK_INDICATOR_FILES self.markers = FRAMEWORK_MARKERS self.priority = 90 def get_priority(self) -> int: return self.priority def detect(self, project_path: Path) -> List[str]: """Detect frameworks from project files. Args: project_path: Path to the project directory. Returns: List of detected framework names. """ detected_frameworks = [] for framework, indicators in self.indicator_files.items(): if self._detect_framework(project_path, framework, indicators): detected_frameworks.append(framework) return detected_frameworks def _detect_framework( self, project_path: Path, framework: str, indicators: List[str] ) -> bool: """Check if a framework is present based on indicators. Args: project_path: Project root directory. framework: Framework name to check. indicators: List of indicator files for the framework. Returns: True if framework is detected, False otherwise. """ for indicator in indicators: matches = list(project_path.rglob(indicator)) if matches: return True return False def detect_from_content(self, project_path: Path) -> List[str]: """Detect frameworks from source code content. Args: project_path: Path to the project directory. Returns: List of detected framework names. """ detected = [] for framework, marker_info in self.markers.items(): content_markers = marker_info.get("content_markers", []) for name, pattern in content_markers: if self._search_content(project_path, pattern): if framework not in detected: detected.append(framework) break return detected def _search_content(self, project_path: Path, pattern: str) -> bool: """Search for a pattern in source files. Args: project_path: Project root directory. pattern: Regex pattern to search for. Returns: True if pattern is found, False otherwise. """ try: regex = re.compile(pattern, re.IGNORECASE) for file_path in project_path.rglob("*"): if file_path.is_file() and self._is_source_file(file_path): content = self._read_file_content(file_path) if content and regex.search(content): return True except (PermissionError, OSError, re.error): pass return False def _is_source_file(self, file_path: Path) -> bool: """Check if file is a source code file. Args: file_path: Path to check. Returns: True if it's a source file. """ source_extensions = [ '.py', '.js', '.ts', '.jsx', '.tsx', '.go', '.java', '.rb', '.php', '.rs', '.swift', '.kt', '.scala', '.c', '.cpp', '.h', '.hpp', '.cs', '.lua', '.ex', '.exs', ] return file_path.suffix.lower() in source_extensions def detect_package_json_frameworks(self, project_path: Path) -> List[str]: """Detect frameworks from package.json dependencies. Args: project_path: Path to the project directory. Returns: List of detected frameworks. """ detected = [] framework_deps = { "react": ["react", "react-dom"], "vue": ["vue"], "angular": ["@angular/core", "@angular/common"], "express": ["express"], "nextjs": ["next"], "nuxt": ["nuxt"], "gatsby": ["gatsby"], "svelte": ["svelte"], "nestjs": ["@nestjs/core"], } try: for package_json in project_path.rglob("package.json"): content = self._read_file_content(package_json) if content: import json try: data = json.loads(content) deps = data.get("dependencies", {}) dev_deps = data.get("devDependencies", {}) all_deps = {**deps, **dev_deps} for framework, packages in framework_deps.items(): for pkg in packages: if pkg in all_deps: if framework not in detected: detected.append(framework) break except json.JSONDecodeError: pass except (PermissionError, OSError): pass return detected