"""Static code analysis checks.""" import re from datetime import datetime from pathlib import Path from typing import Any import git import requests from bs4 import BeautifulSoup from shared.logger import setup_logger logger = setup_logger(__name__) class StaticChecker: """Perform static checks on repository.""" def __init__(self, repo_url: str, commit_sha: str, clone_dir: Path) -> None: """Initialize static checker. Args: repo_url: Repository URL commit_sha: Commit SHA to check clone_dir: Directory to clone repo """ self.repo_url = repo_url self.commit_sha = commit_sha self.clone_dir = clone_dir self.repo = None def clone_repo(self) -> None: """Clone repository and checkout specific commit.""" logger.info(f"Cloning {self.repo_url} to {self.clone_dir}") # Clean up if exists if self.clone_dir.exists(): import shutil shutil.rmtree(self.clone_dir) # Clone self.repo = git.Repo.clone_from(self.repo_url, self.clone_dir) # Checkout specific commit self.repo.git.checkout(self.commit_sha) logger.info(f"Checked out commit {self.commit_sha}") def check_created_after_task(self, task_timestamp: datetime) -> dict[str, Any]: """Check if repo was created after task was sent. Args: task_timestamp: When task was sent Returns: Check result """ try: # Get first commit timestamp commits = list(self.repo.iter_commits()) if not commits: return { "passed": False, "score": 0.0, "reason": "No commits found", } first_commit = commits[-1] first_commit_time = datetime.fromtimestamp(first_commit.committed_date) if first_commit_time > task_timestamp: return { "passed": True, "score": 1.0, "reason": f"Repo created after task ({first_commit_time} > {task_timestamp})", } else: return { "passed": False, "score": 0.0, "reason": f"Repo existed before task ({first_commit_time} <= {task_timestamp})", } except Exception as e: logger.error(f"Error checking repo creation time: {e}") return {"passed": False, "score": 0.0, "reason": f"Error: {e}"} def check_mit_license(self) -> dict[str, Any]: """Check if repo has MIT LICENSE in root. Returns: Check result """ try: license_path = self.clone_dir / "LICENSE" if not license_path.exists(): return { "passed": False, "score": 0.0, "reason": "No LICENSE file found in root", } content = license_path.read_text() if "MIT License" in content or "MIT" in content[:200]: return { "passed": True, "score": 1.0, "reason": "MIT LICENSE found", } else: return { "passed": False, "score": 0.0, "reason": "LICENSE file exists but is not MIT", } except Exception as e: logger.error(f"Error checking LICENSE: {e}") return {"passed": False, "score": 0.0, "reason": f"Error: {e}"} def check_readme(self) -> dict[str, Any]: """Check README.md quality. Returns: Check result """ try: readme_path = self.clone_dir / "README.md" if not readme_path.exists(): return { "passed": False, "score": 0.0, "reason": "No README.md found", } content = readme_path.read_text() # Basic quality checks has_title = bool(re.search(r"^#\s+.+", content, re.MULTILINE)) has_sections = content.count("#") >= 3 has_enough_content = len(content) >= 200 has_code_blocks = "```" in content score = sum([has_title, has_sections, has_enough_content, has_code_blocks]) / 4 return { "passed": score >= 0.75, "score": score, "reason": f"README quality: {score:.0%} (title={has_title}, " f"sections={has_sections}, content={has_enough_content}, code={has_code_blocks})", } except Exception as e: logger.error(f"Error checking README: {e}") return {"passed": False, "score": 0.0, "reason": f"Error: {e}"} def check_no_secrets(self) -> dict[str, Any]: """Check for common secret patterns in git history. Returns: Check result """ try: # Check for common secret patterns secret_patterns = [ r"sk-[a-zA-Z0-9]{32,}", # API keys r"ghp_[a-zA-Z0-9]{36,}", # GitHub tokens r"AKIA[0-9A-Z]{16}", # AWS keys r"password\s*=\s*['\"][^'\"]{8,}", # Hardcoded passwords ] found_secrets = [] # Check all files in current commit for item in self.clone_dir.rglob("*"): if item.is_file() and not item.name.startswith(".git"): try: content = item.read_text(errors="ignore") for pattern in secret_patterns: if re.search(pattern, content, re.IGNORECASE): found_secrets.append(str(item.relative_to(self.clone_dir))) except Exception: pass if found_secrets: return { "passed": False, "score": 0.0, "reason": f"Potential secrets found in: {', '.join(found_secrets[:3])}", } else: return { "passed": True, "score": 1.0, "reason": "No obvious secrets detected", } except Exception as e: logger.error(f"Error checking secrets: {e}") return {"passed": True, "score": 1.0, "reason": "Could not check secrets"}