Coverage / src/refs.ts 84.7%
61 of 72 lines run, 6 of 6 functions, 25 of 27 branches · run #23 on main · run missed
| 1 | 0× | // `#N` and `@user` in text the user is typing (spec §6.2): what is being |
| 2 | 0× | // completed at the cursor, and which reference a position sits on. No |
| 3 | 0× | // vscode imports, so it is tested with plain Node. |
| 4 | 1× | |
| 5 | 0× | /** A reference being typed: `#12` (issue or proposal) or `@an` (a user). */ |
| 6 | export type Typing = { kind: "number"; prefix: string; start: number } | { kind: "user"; prefix: string; start: number }; | |
| 7 | 1× | |
| 8 | 0× | /** |
| 9 | 0× | * What the text before the cursor is in the middle of typing, if anything. |
| 10 | 0× | * A reference starts a word: after the line start, whitespace or an |
| 11 | 0× | * opening bracket, so `a#1` and `mail@host` are not references. |
| 12 | 0× | */ |
| 13 | 9× | export function typingAt(before: string): Typing | undefined { |
| 14 | 9× | const n = /(^|[\s([{])#(\d*)$/.exec(before); |
| 15 | 9× | if (n) { |
| 16 | 4× | return { kind: "number", prefix: n[2], start: before.length - n[2].length - 1 }; |
| 17 | 4× | } |
| 18 | 5× | const u = /(^|[\s([{])@([A-Za-z0-9][A-Za-z0-9_-]*|)$/.exec(before); |
| 19 | 9× | if (u) { |
| 20 | 2× | return { kind: "user", prefix: u[2], start: before.length - u[2].length - 1 }; |
| 21 | 2× | } |
| 22 | 9× | return undefined; |
| 23 | 5× | } |
| 24 | 5× | |
| 25 | 5× | /** The `#N` reference covering `column` of `line`, with its span. */ |
| 26 | 5× | export function numberAt(line: string, column: number): { number: number; start: number; end: number } | undefined { |
| 27 | 5× | const re = /(^|[\s([{])#(\d+)\b/g; |
| 28 | 5× | for (let m = re.exec(line); m; m = re.exec(line)) { |
| 29 | 8× | const start = m.index + m[1].length; |
| 30 | 8× | const end = start + 1 + m[2].length; |
| 31 | 8× | if (column >= start && column <= end) { |
| 32 | 3× | return { number: Number(m[2]), start, end }; |
| 33 | 3× | } |
| 34 | 8× | } |
| 35 | 5× | return undefined; |
| 36 | 3× | } |
| 37 | 3× | |
| 38 | 3× | /** A repository root named by an SCM input box's URI query (`rootUri=file:///…`). */ |
| 39 | 3× | export function rootFromScmQuery(query: string): string | undefined { |
| 40 | 3× | const raw = new URLSearchParams(query).get("rootUri"); |
| 41 | 3× | if (!raw) { |
| 42 | 1× | return undefined; |
| 43 | 1× | } |
| 44 | 2× | try { |
| 45 | 2× | return decodeURIComponent(new URL(raw).pathname); |
| 46 | 3× | } catch { |
| 47 | 0× | return undefined; |
| 48 | 0× | } |
| 49 | 4× | } |
| 50 | 4× | |
| 51 | 4× | /** The note of a `TODO`/`FIXME`/`XXX`/`HACK` comment line, as an issue title. */ |
| 52 | 4× | export function todoTitle(line: string): string | undefined { |
| 53 | 4× | const m = /\b(TODO|FIXME|XXX|HACK)\b(?:\([^)]*\))?[:\s-]*(.*)$/.exec(line); |
| 54 | 4× | if (!m) { |
| 55 | 1× | return undefined; |
| 56 | 1× | } |
| 57 | 3× | const text = m[2].replace(/\s*(\*\/|-->|#\}|\*\)|;)\s*$/, "").trim(); |
| 58 | 4× | return text ? text : `${m[1]} in the code`; |
| 59 | 2× | } |
| 60 | 2× | |
| 61 | 2× | /** The TODO line with the new issue's number recorded: `TODO(#12): …`. */ |
| 62 | 2× | export function annotateTodo(line: string, number: number): string { |
| 63 | 2× | return line.replace(/\b(TODO|FIXME|XXX|HACK)\b(?!\()/, `$1(#${number})`); |
| 64 | 2× | } |
| 65 | 1× | |
| 66 | 1× | /** An issue body quoting the code, with a permalink to it on the hub. */ |
| 67 | 1× | export function issueBody(opts: { permalink: string; path: string; startLine: number; endLine: number; language: string; code: string; pinned: boolean }): string { |
| 68 | 2× | const where = opts.startLine === opts.endLine ? `line ${opts.startLine}` : `lines ${opts.startLine}–${opts.endLine}`; |
| 69 | 2× | const fence = opts.code.includes("```") ? "~~~" : "```"; |
| 70 | 2× | const note = opts.pinned ? "" : "\n\n_The link follows the bookmark, which has several heads, so the lines may move._"; |
| 71 | 2× | return `From [\`${opts.path}\`, ${where}](${opts.permalink}):\n\n${fence}${opts.language}\n${opts.code.replace(/\n$/, "")}\n${fence}${note}\n`; |
| 72 | 2× | } |