ownEffects

tcmts.core.effects.ownEffects · #bg4vgt5f · term · runs anywhere

What one definition's own code (its stored IR) reaches outside the codebase, not counting what it uses: from Node's modules it imports, the globals it reads (fetch, process.env, Date.now(), new Date(), Math.random()), and which of their members. What's only named in types (b: Buffer, typeof process in a type, a type imported from node:fs) is erased, so it reaches nothing. What npm packages do inside isn't seen.

import {
  EFFECT_EXPORTS, EFFECT_GLOBALS, EFFECT_MEMBERS, EFFECT_MODULES, QUIET_PROCESS,
} from "#codebase/tcmts/core/effects";
import type { Effect } from "#codebase/tcmts/core/effects";
import type { IR } from "#codebase/tcmts/core/hash";
import { isErased } from "#codebase/tcmts/core/ir";

export function ownEffects(ir: unknown): Effect[] {
  const found = new Set<Effect>();
  const child = (node: unknown[], key: string): unknown =>
    (node.find((c) => Array.isArray(c) && c[0] === key) as unknown[] | undefined)?.[1];
  const isRef = (n: unknown, tag: string): n is unknown[] => Array.isArray(n) && n[0] === tag;
  /** `G` → its name; `Ext` → `module`, or `module#export` for a named import */
  const named = (n: unknown): string | undefined =>
    isRef(n, "G")
      ? String(n[1])
      : isRef(n, "Ext")
        ? n[3] === "default" || n[3] === "*"
          ? String(n[1])
          : undefined
        : undefined;
  const visit = (node: unknown, member?: boolean) => {
    if (!Array.isArray(node) || isErased(node as IR)) return;
    const tag = node[0];
    if (tag === "Ext") {
      const mod = String(node[1]);
      const e = EFFECT_MODULES[mod] ?? EFFECT_EXPORTS[`${mod}#${String(node[3])}`];
      if (e) found.add(e);
      return;
    }
    if (tag === "G") {
      const name = String(node[1]);
      const e = EFFECT_GLOBALS[name];
      if (e) found.add(e);
      // `process` itself, passed around or destructured, could be read for anything
      else if (name === "process" && !member) found.add("environment");
      return;
    }
    if (tag === "PropertyAccessExpression") {
      const target = child(node, "expression");
      const name = child(node, "name");
      const owner = named(target);
      if (owner && isRef(name, "N")) {
        const key = `${owner}.${String(name[1])}`;
        const e = EFFECT_MEMBERS[key];
        if (e) found.add(e);
        else if (owner === "process" && !QUIET_PROCESS.has(String(name[1])))
          found.add("environment");
        visit(target, true);
        return;
      }
    }
    // `new Date()` and `Date()`: now
    if (
      (tag === "NewExpression" || tag === "CallExpression") &&
      isRef(child(node, "expression"), "G")
    ) {
      const callee = child(node, "expression") as unknown[];
      const args = child(node, "arguments");
      if (
        callee[1] === "Date" &&
        (tag === "CallExpression" || !Array.isArray(args) || args.length <= 1)
      )
        found.add("clock");
    }
    for (const c of node) visit(c);
  };
  visit(typeof ir === "string" ? JSON.parse(ir) : ir);
  return [...found].sort();
}

uses

used by