nameInScratch

tcmts.studio.nameInScratch · #2cxfmong · term · runs anywhere

The name at offset in a scratch file, as references and renaming see it: the identifier, its symbol here, where the file uses it (offsets, its own declaration or import included), and, for a name the codebase has (one it imports, or a definition the file holds that's saved), that name and its hash.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { classify } from "#codebase/tcmts/cli/main";
import type { Analysis } from "#codebase/tcmts/core/analyze";
import { cacheRefOf } from "#codebase/tcmts/core/shape";
import { declarationOf } from "#codebase/tcmts/core/ts";
import { identifierAt } from "#codebase/tcmts/studio";
import { isImportSpecifier as ts_isImportSpecifier } from "typescript/ast";
import type { Node as ts_Node } from "typescript/ast";
import { SymbolFlags as ts_SymbolFlags } from "typescript/sync";

export function nameInScratch(cb: Codebase, a: Analysis, abs: string, offset: number) {
  const sf = a.program.getSourceFile(abs);
  const id = identifierAt(a, abs, offset);
  if (!sf || !id) return undefined;
  const checker = a.checker;
  const sym = checker.getSymbolAtLocation(id);
  if (!sym) return undefined;
  const nodes = new Map<number, ts_Node>();
  try {
    for (const h of checker.getReferencesToSymbolInFile(abs, sym)) {
      const n = h.resolve() as ts_Node | undefined;
      if (n) nodes.set(n.getStart(sf), n);
    }
  } catch {
    // (the compiler has let this program go: the identifier itself, then)
  }
  nodes.set(id.getStart(sf), id);
  const decl = declarationOf(sym);
  const named = decl && (decl as ts_Node & { name?: ts_Node }).name;
  if (named && named.getSourceFile() === sf && named.getText(sf) === id.text)
    nodes.set(named.getStart(sf), named);
  const uses = [...nodes.values()]
    .filter((n) => n.getText(sf) === id.text)
    .map((n) => ({ from: n.getStart(sf), to: n.end }))
    .sort((x, y) => x.from - y.from);
  const target = sym.flags & ts_SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
  let hash = cacheRefOf(checker)(target);
  let name = hash ? cb.store.pathsOf(cb.store.patched(hash))[0] : undefined;
  // (a definition of the file's own that's saved: the codebase has its name)
  if (!name && named && !a.diagnostics.length && !a.errors.length) {
    const def = a.defs.find((d) => d.range.nameStart === named.getStart(sf));
    const row = def && classify(cb, a.defs, a.namespace ?? "").find((r) => r.def === def);
    if (row && (row.status === "identical" || row.status === "update")) {
      name = row.path;
      hash = cb.lookup(row.path)[0];
    }
  }
  const spec = decl && ts_isImportSpecifier(decl) ? decl : undefined;
  return {
    id,
    sf,
    sym,
    uses,
    spec,
    imported: !!(sym.flags & ts_SymbolFlags.Alias),
    ...(name && hash ? { name, hash } : {}),
  };
}

uses

used by