Skip to the content

Capsid menu

app/lib/media/template-refs.mjs

107 lines and 8 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of template-refs.mjs
FunctionLinesComplexityCoveredCRAPMutation
isSourceFile25 to 326100%WatchCRAP 6Watch78.6%
<anonymous:28>28 to 282100%OkCRAP 2Ok100%
<anonymous:31>31 to 311100%OkCRAP 1Ok100%
stripComments42 to 7123100%WatchCRAP 23Watch61.3%
referencesIn81 to 951100%OkCRAP 1Ok100%
<anonymous:82>82 to 946100%WatchCRAP 6Ok89.5%
bounded90 to 902100%OkCRAP 2Ok87.5%
foldRefs103 to 1167100%WatchCRAP 7Ok82.4%

Imports

It imports no other module of this repo. No module of this repo imports it.

Source

// Runs at BUILD time and ships as a committed artifact: the Worker has no repository, and its ASSETS
// binding can only fetch. The match is LITERAL, so a constructed path like `/diagrams/${id}.svg` is
// invisible; that is why the library says "no reference found" rather than "unused".

// `content/posts` is absent: post citations belong to the other tracker, and an asset must not claim both.
export const SOURCE_ROOTS = ["app", "workers"];

export const SOURCE_FILES = ["public/site.webmanifest", "content/features.json"];

// Each paper's file names its PDF by path (`pdfPath`) and each dictionary entry its pronunciation clip (`audio`), so
// these markdown files are sources: without them every hosted PDF and clip would read as unreferenced. Passed
// through unstripped, like JSON: the extracted text has apostrophes. Each cohort's file under content/roster names its
// photograph by path, so without it the nine photographs would read as unreferenced.
export const SOURCE_DIRECTORIES = ["content/publications", "content/dictionary", "content/roster"];

const SOURCE_EXTENSIONS = [".ts", ".tsx", ".mjs", ".js", ".css", ".json", ".webmanifest"];

// Checked first because `SOURCE_FILES` bypass the root rule. `assets.json` lists every asset path.
const SELF_REFERENTIAL = ["content/generated/assets.json"];

/**
 * @param {string} file repo-relative, forward slashes
 * @returns {boolean}
 */
export function isSourceFile(file) {
  if (SELF_REFERENTIAL.includes(file)) return false;
  if (SOURCE_FILES.includes(file)) return true;
  if (SOURCE_DIRECTORIES.some((dir) => file.startsWith(`${dir}/`) && file.endsWith(".md"))) return true;
  const root = file.split("/")[0] ?? "";
  if (!SOURCE_ROOTS.includes(root)) return false;
  return SOURCE_EXTENSIONS.some((ext) => file.endsWith(ext));
}

/**
 * Prose about an asset is not a reference. A tokenizer, not a regex: stripping from `//` to end of line
 * would destroy every `https://` inside a string literal, which is where real references live.
 *
 * @param {string} text
 * @param {boolean} [cssOnly] true for `.css`, which has no line comments
 * @returns {string} the same length, with comment bytes blanked to spaces
 */
export function stripComments(text, cssOnly = false) {
  let out = "";
  let i = 0;
  /** @type {"code"|"line"|"block"|"'"|"\""|"`"} */
  let mode = "code";
  while (i < text.length) {
    const c = text[i];
    const next = text[i + 1];
    if (mode === "code") {
      if (c === "/" && next === "*") { mode = "block"; out += "  "; i += 2; continue; }
      if (!cssOnly && c === "/" && next === "/") { mode = "line"; out += "  "; i += 2; continue; }
      if (!cssOnly && (c === "'" || c === '"' || c === "`")) { mode = c; out += c; i += 1; continue; }
      if (cssOnly && (c === "'" || c === '"')) { mode = c; out += c; i += 1; continue; }
      out += c; i += 1; continue;
    }
    if (mode === "line") {
      if (c === "\n") { mode = "code"; out += c; i += 1; continue; }
      // Blanked rather than deleted, so offsets stay comparable and lines cannot join.
      out += " "; i += 1; continue;
    }
    if (mode === "block") {
      if (c === "*" && next === "/") { mode = "code"; out += "  "; i += 2; continue; }
      out += c === "\n" ? c : " "; i += 1; continue;
    }
    if (c === "\\") { out += text.slice(i, i + 2); i += 2; continue; }
    if (c === mode) { mode = "code"; out += c; i += 1; continue; }
    out += c; i += 1;
  }
  return out;
}

/**
 * Both ends must be boundaries: `/dustin-edwards-logo.svg` is a prefix of `/dustin-edwards-logo-dark.svg`,
 * and `"public/site.webmanifest"` ends with `/site.webmanifest`.
 *
 * @param {string} text the file contents
 * @param {string[]} assetPaths every known asset path
 * @returns {string[]} the subset referenced, in the order given
 */
export function referencesIn(text, assetPaths) {
  return assetPaths.filter((asset) => {
    let from = 0;
    for (;;) {
      const at = text.indexOf(asset, from);
      if (at < 0) return false;
      const before = at === 0 ? undefined : text[at - 1];
      const after = text[at + asset.length];
      /** @param {string | undefined} ch */
      const bounded = (ch) => ch === undefined || !/[A-Za-z0-9._~/-]/.test(ch);
      if (bounded(before) && bounded(after)) return true;
      from = at + 1;
    }
  });
}

/**
 * Sorted, so the generated artifact does not churn with directory order and a gate can byte-compare it.
 *
 * @param {Array<{ file: string, assets: string[] }>} scanned
 * @returns {{ generated: number, refs: Record<string, string[]> }}
 */
export function foldRefs(scanned) {
  /** @type {Record<string, string[]>} */
  const refs = {};
  for (const { file, assets } of scanned) {
    for (const asset of assets) {
      if (!refs[asset]) refs[asset] = [];
      if (!refs[asset].includes(file)) refs[asset].push(file);
    }
  }
  /** @type {Record<string, string[]>} */
  const sorted = {};
  for (const asset of Object.keys(refs).sort()) sorted[asset] = (refs[asset] ?? []).slice().sort();
  return { generated: Object.keys(sorted).length, refs: sorted };
}