97 lines
2.8 KiB
Python
97 lines
2.8 KiB
Python
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
|