diff --git a/src/depcheck/analyzers/version.py b/src/depcheck/analyzers/version.py new file mode 100644 index 0000000..acf7e87 --- /dev/null +++ b/src/depcheck/analyzers/version.py @@ -0,0 +1,95 @@ +"""Version analysis utilities.""" + +from typing import Optional + +from depcheck.models import Severity + + +class VersionAnalyzer: + """Analyze dependency versions for update recommendations.""" + + def __init__(self): + self._bump_recommendations = { + "major": "major", + "minor": "minor", + "patch": "patch", + } + + def get_update_type(self, current: str, latest: str) -> Optional[str]: + """Determine the type of version bump needed.""" + if not current or not latest: + return None + + current_parts = self._parse_version(current) + latest_parts = self._parse_version(latest) + + if not current_parts or not latest_parts: + return None + + if latest_parts[0] > current_parts[0]: + return "major" + if len(latest_parts) > 1 and len(current_parts) > 1: + if latest_parts[1] > current_parts[1]: + return "minor" + if len(latest_parts) > 2 and len(current_parts) > 2: + if latest_parts[2] > current_parts[2]: + return "patch" + + return None + + def _parse_version(self, version: str) -> tuple: + """Parse version string into tuple of integers.""" + version = version.strip().lstrip("vV") + parts = [] + for part in version.split("."): + cleaned = "".join(c for c in part if c.isdigit()) + if cleaned: + parts.append(int(cleaned)) + else: + parts.append(0) + return tuple(parts) + + def get_severity_for_update(self, update_type: Optional[str]) -> Severity: + """Map update type to severity level.""" + mapping = { + "major": Severity.HIGH, + "minor": Severity.MEDIUM, + "patch": Severity.LOW, + } + if update_type is None: + return Severity.INFO + return mapping.get(update_type, Severity.INFO) + + def suggest_safe_upgrade(self, current: str, update_type: str) -> str: + """Suggest a safe upgrade version based on current version and update type.""" + parts = self._parse_version(current) + if not parts: + return current + + while len(parts) < 3: + parts = parts + (0,) + + if update_type == "major": + return f"{parts[0] + 1}.0.0" + elif update_type == "minor": + return f"{parts[0]}.{parts[1] + 1}.0" + elif update_type == "patch": + return f"{parts[0]}.{parts[1]}.{parts[2] + 1}" + + return current + + def compare_versions(self, v1: str, v2: str) -> int: + """Compare two version strings.""" + parts1 = self._parse_version(v1) + parts2 = self._parse_version(v2) + + for i in range(max(len(parts1), len(parts2))): + p1 = parts1[i] if i < len(parts1) else 0 + p2 = parts2[i] if i < len(parts2) else 0 + + if p1 > p2: + return 1 + elif p1 < p2: + return -1 + + return 0