from __future__ import annotations from vuln_scan.core.models import Finding from vuln_scan.reporting.base import Reporter from vuln_scan.reporting.rich_reporter import RichReporter class MarkdownReporter(Reporter): """Renders findings as a GitHub-flavored Markdown table for PR comments.""" def render(self, findings: list[Finding], dependabot_url: str) -> str: if not findings: print("✓ No vulnerabilities found.") return "✅ **No vulnerabilities found.**" self._print_terminal(findings, dependabot_url) rows = [self.extract_row(f) for f in findings] blocking_count, non_blocking_count = self.summary_counts(findings) lines: list[str] = [] lines.append( "| File | Package | Current Version | Min. Required Version " "| Vulnerable Range | Severity | Blocking " "| Grace Period | Blocking Date | Reason |" ) lines.append("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |") for row in rows: lines.append( f"| `{row.file}` " f"| `{row.package}` " f"| `{row.version}` " f"| `{row.patch}` " f"| `{row.vuln_range}` " f"| {self.severity_badge(row.severity)} " f"| {self.blocking_badge(row.is_blocking)} " f"| `{row.grace_period}` " f"| `{row.blocking_date}` " f"| {row.reason} |" ) lines.append("") lines.append( f"**Total findings:** {len(findings)} " f"({blocking_count} blocking, {non_blocking_count} non-blocking)" ) return "\n".join(lines) def _print_terminal(self, findings: list[Finding], dependabot_url: str) -> None: reporter = RichReporter() reporter.render(findings, dependabot_url)