diff --git a/src/detectors/framework.py b/src/detectors/framework.py new file mode 100644 index 0000000..3919d44 --- /dev/null +++ b/src/detectors/framework.py @@ -0,0 +1,162 @@ +"""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