1import {
2 findAndReplaceText,
3 type MarkdownNode,
4 type TextMatch,
5} from "~/vendor/mdast-find-and-replace";
6
7export {
8 splitPullRequestBody,
9 type PullRequestBodySegment,
10} from "@t3tools/client-runtime/pull-request-markdown";
11
12const AUTOLINK_CANDIDATE_PATTERN = /#[1-9]\d*|[0-9a-f]{40}/giu;
13const AUTOLINK_WORD_CHARACTER_PATTERN = /[A-Za-z0-9_]/u;
14const AUTOLINK_COMMIT_PREFIX_PATTERN = /[\s([{]/u;
15const AUTOLINK_IGNORED_TYPES = new Set(["link", "linkReference"]);
16
17/** GitHub-style same-repository references for pull request prose, never code or authored links. */
18export function remarkPullRequestAutolinks(options: { readonly repositoryUrl: string }) {
19 const repositoryUrl = options.repositoryUrl.replace(/\/+$/u, "");
20 return (tree: MarkdownNode) => {
21 findAndReplaceText(
22 tree,
23 AUTOLINK_CANDIDATE_PATTERN,
24 (matched: string, match: TextMatch) => {
25 const reference = matched.startsWith("#");
26 const before = match.input[match.index - 1];
27 const after = match.input[match.index + matched.length];
28 if (
29 (before !== undefined &&
30 (reference
31 ? AUTOLINK_WORD_CHARACTER_PATTERN.test(before)
32 : !AUTOLINK_COMMIT_PREFIX_PATTERN.test(before))) ||
33 (after !== undefined && AUTOLINK_WORD_CHARACTER_PATTERN.test(after))
34 ) {
35 return false;
36 }
37 return {
38 type: "link",
39 url: reference
40 ? `${repositoryUrl}/issues/${matched.slice(1)}`
41 : `${repositoryUrl}/commit/${matched}`,
42 data: {
43 hProperties: {
44 dataPullRequestAutolink: reference ? "reference" : "commit",
45 },
46 },
47 children: [{ type: "text", value: reference ? matched : matched.slice(0, 7) }],
48 };
49 },
50 AUTOLINK_IGNORED_TYPES,
51 );
52 };
53}