Skip to content

Commit 802ff0f

Browse files
committed
Autolink gh-NNNNN to python/cpython issue
1 parent a3fdc36 commit 802ff0f

2 files changed

Lines changed: 21 additions & 19 deletions

File tree

‎content/posts/python-3148-31316-31215-31117-31022/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ It's the big release week with Python 3.15.0 due out tomorrow, but before then,
1616

1717
## Security content in these releases
1818

19-
* CVE-2026-99999 Test
19+
* CVE-2026-99999 gh-12345 Test
2020

2121
* [gh-TODO](https://github.com/python/cpython/issues/TODO): TODO
2222

‎src/plugins/remark-python-refs.ts‎

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
* - CVE references (nvd.nist.gov/vuln/detail/CVE-YYYY-NNNNN)
1515
* - Python releases (python.org/downloads/release/python-XXXX/)
1616
*
17-
* Bare "CVE-YYYY-NNNN" text (not already inside a link) is autolinked
18-
* to the CVE record and rendered as a badge.
17+
* Bare "gh-NNNN" and "CVE-YYYY-NNNN" text (not already inside a link
18+
* or heading) is autolinked and rendered as a badge.
1919
*/
2020
import type { Root, Link, Paragraph, PhrasingContent } from "mdast";
2121
import { SKIP, visit } from "unist-util-visit";
@@ -38,9 +38,14 @@ const DOCS = /^https?:\/\/docs\.python\.org\//i;
3838
const PYPI = /^https?:\/\/pypi\.org\/project\/([^/]+)\/?/i;
3939
const GH_ISSUE = /^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/(issues|pull)\/(\d+)\/?/i;
4040
const CVE = /^https?:\/\/nvd\.nist\.gov\/vuln\/detail\/(CVE-[\d-]+)\/?/i;
41-
/** Bare CVE IDs in plain text, e.g. "CVE-2026-19445" */
42-
const CVE_TEXT = /\bCVE-\d{4}-\d{4,}\b/g;
43-
const cveUrl = (id: string) => `https://www.cve.org/CVERecord?id=${id}`;
41+
42+
/**
43+
* Bare references in plain text that get autolinked (outside links,
44+
* headings and code):
45+
* - "gh-156293" → python/cpython issue
46+
* - "CVE-2026-19445" → cve.org record
47+
*/
48+
const BARE_REF = /\b(?:(CVE-\d{4}-\d{4,})|gh-(\d+))\b/g;
4449
const PY_RELEASE = /^https?:\/\/(?:www\.)?python\.org\/downloads\/release\/(python-[\w.]+)\/?/i;
4550
const GITHUB =
4651
/^https?:\/\/github\.com\/([\w.-]+)(?:\/([\w.-]+))?\/?$/i;
@@ -336,7 +341,7 @@ export default function remarkPythonRefs() {
336341
}
337342
});
338343

339-
// Pass 3: Autolink bare CVE IDs in text → badges
344+
// Pass 3: Autolink bare gh-NNNN issue refs and CVE IDs in text → badges
340345
visit(tree, (node: any, index, parent: any) => {
341346
// Don't touch text that is already a link (or a reference definition),
342347
// or headings — Astro builds heading ids from text nodes only, so
@@ -352,26 +357,23 @@ export default function remarkPythonRefs() {
352357
if (node.type !== "text" || index == null || !parent) return;
353358

354359
const value: string = node.value;
355-
CVE_TEXT.lastIndex = 0;
356-
if (!CVE_TEXT.test(value)) {
357-
CVE_TEXT.lastIndex = 0;
358-
return;
359-
}
360-
361360
const parts: any[] = [];
362361
let lastIndex = 0;
363-
CVE_TEXT.lastIndex = 0;
362+
BARE_REF.lastIndex = 0;
364363
let m: RegExpExecArray | null;
365-
while ((m = CVE_TEXT.exec(value)) !== null) {
364+
while ((m = BARE_REF.exec(value)) !== null) {
366365
if (m.index > lastIndex) {
367366
parts.push({ type: "text", value: value.slice(lastIndex, m.index) });
368367
}
369-
const id = m[0];
370-
const url = cveUrl(id);
371-
collectRef("cve", id, url);
372-
parts.push({ type: "html", value: buildBadgeHtml({ type: "cve", icon: shieldIcon, label: id, url }) });
368+
const [label, cve, ghNum] = m;
369+
const match: Match = cve
370+
? { type: "cve", icon: shieldIcon, label, url: `https://www.cve.org/CVERecord?id=${cve}` }
371+
: { type: "gh-issue", icon: issueIcon, label, url: `https://github.com/python/cpython/issues/${ghNum}` };
372+
collectRef(match.type, match.label, match.url);
373+
parts.push({ type: "html", value: buildBadgeHtml(match) });
373374
lastIndex = m.index + m[0].length;
374375
}
376+
if (parts.length === 0) return;
375377
if (lastIndex < value.length) {
376378
parts.push({ type: "text", value: value.slice(lastIndex) });
377379
}

0 commit comments

Comments
 (0)