OXYGENHUB

Coverage / src/refs.ts 84.7%

61 of 72 lines run, 6 of 6 functions, 25 of 27 branches · run #24 on main · run missed

10×
// `#N` and `@user` in text the user is typing (spec §6.2): what is being
20×
// completed at the cursor, and which reference a position sits on. No
30×
// vscode imports, so it is tested with plain Node.
41×
50×
/** 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 };
71×
80×
/**
90×
 * What the text before the cursor is in the middle of typing, if anything.
100×
 * A reference starts a word: after the line start, whitespace or an
110×
 * opening bracket, so `a#1` and `mail@host` are not references.
120×
 */
139×
export function typingAt(before: string): Typing | undefined {
149×
  const n = /(^|[\s([{])#(\d*)$/.exec(before);
159×
  if (n) {
164×
    return { kind: "number", prefix: n[2], start: before.length - n[2].length - 1 };
174×
  }
185×
  const u = /(^|[\s([{])@([A-Za-z0-9][A-Za-z0-9_-]*|)$/.exec(before);
199×
  if (u) {
202×
    return { kind: "user", prefix: u[2], start: before.length - u[2].length - 1 };
212×
  }
229×
  return undefined;
235×
}
245×
255×
/** The `#N` reference covering `column` of `line`, with its span. */
265×
export function numberAt(line: string, column: number): { number: number; start: number; end: number } | undefined {
275×
  const re = /(^|[\s([{])#(\d+)\b/g;
285×
  for (let m = re.exec(line); m; m = re.exec(line)) {
298×
    const start = m.index + m[1].length;
308×
    const end = start + 1 + m[2].length;
318×
    if (column >= start && column <= end) {
323×
      return { number: Number(m[2]), start, end };
333×
    }
348×
  }
355×
  return undefined;
363×
}
373×
383×
/** A repository root named by an SCM input box's URI query (`rootUri=file:///…`). */
393×
export function rootFromScmQuery(query: string): string | undefined {
403×
  const raw = new URLSearchParams(query).get("rootUri");
413×
  if (!raw) {
421×
    return undefined;
431×
  }
442×
  try {
452×
    return decodeURIComponent(new URL(raw).pathname);
463×
  } catch {
470×
    return undefined;
480×
  }
494×
}
504×
514×
/** The note of a `TODO`/`FIXME`/`XXX`/`HACK` comment line, as an issue title. */
524×
export function todoTitle(line: string): string | undefined {
534×
  const m = /\b(TODO|FIXME|XXX|HACK)\b(?:\([^)]*\))?[:\s-]*(.*)$/.exec(line);
544×
  if (!m) {
551×
    return undefined;
561×
  }
573×
  const text = m[2].replace(/\s*(\*\/|-->|#\}|\*\)|;)\s*$/, "").trim();
584×
  return text ? text : `${m[1]} in the code`;
592×
}
602×
612×
/** The TODO line with the new issue's number recorded: `TODO(#12): …`. */
622×
export function annotateTodo(line: string, number: number): string {
632×
  return line.replace(/\b(TODO|FIXME|XXX|HACK)\b(?!\()/, `$1(#${number})`);
642×
}
651×
661×
/** An issue body quoting the code, with a permalink to it on the hub. */
671×
export function issueBody(opts: { permalink: string; path: string; startLine: number; endLine: number; language: string; code: string; pinned: boolean }): string {
682×
  const where = opts.startLine === opts.endLine ? `line ${opts.startLine}` : `lines ${opts.startLine}–${opts.endLine}`;
692×
  const fence = opts.code.includes("```") ? "~~~" : "```";
702×
  const note = opts.pinned ? "" : "\n\n_The link follows the bookmark, which has several heads, so the lines may move._";
712×
  return `From [\`${opts.path}\`, ${where}](${opts.permalink}):\n\n${fence}${opts.language}\n${opts.code.replace(/\n$/, "")}\n${fence}${note}\n`;
722×
}