Go · 2969 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 // Package markdown is shithub's canonical markdown rendering pipeline.
4 //
5 // One entry point: `Render(ctx, source, opts) (html, refs, mentions)`.
6 // Every comment, issue/PR body, README, and any future surface that
7 // takes user-authored markdown flows through here. No other code in
8 // the tree imports goldmark or bluemonday directly; a lint guard at
9 // `scripts/lint-markdown-boundary.sh` enforces that boundary.
10 //
11 // What's supported (CommonMark + a curated GFM set):
12 //
13 // - Headings, paragraphs, lists, blockquotes, code blocks (fenced + indented).
14 // - GFM tables, strikethrough, autolinks, task lists.
15 // - `@username` mentions — resolved via Options.Resolvers.User.
16 // - `#N` and `owner/repo#N` references — resolved via
17 // Options.Resolvers.Issue, gated by viewer visibility.
18 // - Emoji shortcodes (`:smile:`) — curated set in `emoji/`.
19 // - `<details>` / `<summary>`, `<kbd>`, `<sup>`, `<sub>`,
20 // task-list checkboxes (output by Goldmark with `disabled`).
21 // - Code-block class allowlist: `language-*` (Chroma uses these).
22 //
23 // What's deliberately not supported:
24 //
25 // - Raw HTML beyond the strict allowlist (we do NOT match GitHub's
26 // loose HTML acceptance — we err safer).
27 // - `data:` URIs (any flavor; documented in docs/markdown.md).
28 // - `javascript:` / `vbscript:` URLs (rejected by sanitizer).
29 // - GFM Footnotes (deferred).
30 // - Math (KaTeX), Mermaid, embedded media (post-MVP).
31 //
32 // Pipeline-version contract:
33 //
34 // - `Version` (in version.go) stamps every render.
35 // - Callers store the version alongside cached HTML.
36 // - On read, callers compare the stored version to `Version`; if
37 // they differ, the cache is stale and the caller re-renders. We
38 // never run a one-shot "re-render every comment" job — lazy.
39 //
40 // Performance budget:
41 //
42 // - 50 KiB body, full extensions: <30 ms p99 on MVP hardware.
43 // - Inputs above MaxRenderInputBytes are rejected up-front; callers
44 // enforce the matching cap at the API layer.
45 package markdown
46
47 // MaxRenderInputBytes caps the input body. Comments / bodies are
48 // rejected at the API layer at 64 KiB or 256 KiB depending on
49 // surface; this is the renderer's defensive fallback.
50 const MaxRenderInputBytes = 1 << 20 // 1 MiB
51
52 // Ref is one resolved reference produced during rendering. The
53 // caller uses these for downstream notification fan-out (S29) and
54 // for the issue_references index (S21).
55 type Ref struct {
56 // Kind is "issue" or "commit" for now.
57 Kind string
58 // Same-repo refs leave Owner/Repo empty.
59 Owner string
60 Repo string
61 // Number is set for issue refs; FullSHA for commit refs.
62 Number int64
63 FullSHA string
64 // Href is the resolved URL the renderer wrote into the document.
65 Href string
66 }
67
68 // Mention is one resolved @username mention. S29's fan-out consumes
69 // the deduplicated list returned by Render.
70 type Mention struct {
71 Username string
72 Href string
73 }
74