from typing import Optional class ValidationIssue: def __init__(self, issue_type: str, message: str, severity: str, line_number: int = 0): self.issue_type = issue_type self.message = message self.severity = severity self.line_number = line_number def __repr__(self): return f"ValidationIssue({self.issue_type}, {self.severity}, line {self.line_number})" class Validator: def __init__(self): pass def validate_pattern(self, pattern: str) -> Optional[ValidationIssue]: if not pattern or pattern.startswith("#"): return None pattern = pattern.strip() if not pattern: return None if "\\n" in pattern: return ValidationIssue( "invalid_escape", "Backslash-n in pattern is invalid. Use / instead.", "error", ) if pattern.rstrip().endswith("/") and not pattern.endswith("**"): return ValidationIssue( "trailing_slash", "Directory pattern should not have trailing slash", "warning", ) if "**" in pattern and not ( pattern.startswith("**/") or pattern.endswith("/**") or pattern == "**" ): return ValidationIssue( "double_star", "Double star pattern may not work as expected", "warning", ) negations = [p for p in pattern.split() if p.startswith("!")] if len(negations) > 1: return ValidationIssue( "double_negation", "Multiple negations in same line", "warning", ) if pattern in ["!.gitignore", "!.gitignore/*"]: return ValidationIssue( "gitignore_rule", "Pattern tries to negate .gitignore itself", "error", ) return None def get_summary(self, content: str) -> dict: issues = [] valid = True errors = 0 warnings = 0 for i, line in enumerate(content.splitlines(), 1): issue = self.validate_pattern(line) if issue: issue.line_number = i issues.append(issue) if issue.severity == "error": valid = False errors += 1 else: warnings += 1 return { "valid": valid, "errors": errors, "warnings": warnings, "issues": issues, "lines": len(content.splitlines()), } def is_valid(self, pattern: str) -> bool: return self.validate_pattern(pattern) is None