writtenOutRefs

tcmts.core.ir.writtenOutRefs · #vi525ixt · term · runs anywhere

The definitions code only writes out as source text, never running them: ${f} in a template, f.toString(), String(f). A function whose source goes into a file something else runs (a generated runtime, a script run with node -e) runs there, not in the code writing it out, so what it needs (the DOM, Node, the clock) isn't that code's. A definition the code also uses any other way (calls it, passes it along) isn't one.

import type { IR } from "#codebase/tcmts/core/hash";
import { childOf } from "#codebase/tcmts/core/ir";

export function writtenOutRefs(ir: string | IR): Set<string> {
  const written = new Set<string>();
  const used = new Set<string>();
  const isRef = (n: IR | undefined): n is [string, string] =>
    Array.isArray(n) && n[0] === "Ref" && typeof n[1] === "string";
  /** the reference `node` writes out as text, if it's one of those forms */
  const writes = (node: IR[]): IR | undefined => {
    if (node[0] === "TemplateSpan") return childOf(node, "expression");
    if (node[0] !== "CallExpression") return undefined;
    const callee = childOf(node, "expression");
    const args = childOf(node, "arguments");
    const list = Array.isArray(args) ? args.slice(1) : [];
    if (Array.isArray(callee) && callee[0] === "G" && callee[1] === "String" && list.length === 1)
      return list[0];
    if (Array.isArray(callee) && callee[0] === "PropertyAccessExpression" && !list.length) {
      const name = childOf(callee, "name");
      if (Array.isArray(name) && name[0] === "N" && name[1] === "toString")
        return childOf(callee, "expression");
    }
    return undefined;
  };
  const visit = (node: IR) => {
    if (!Array.isArray(node)) return;
    if (isRef(node)) {
      used.add(node[1]);
      return;
    }
    const w = writes(node);
    // (a template's other part is its text: nothing else of these forms holds code)
    if (isRef(w)) {
      written.add(w[1]);
      return;
    }
    for (const c of node) visit(c);
  };
  visit(typeof ir === "string" ? (JSON.parse(ir) as IR) : ir);
  return new Set([...written].filter((h) => !used.has(h)));
}

uses

used by