prefetched

tcmts.core.analyze.prefetched · #axj4hf2a · term · runs on the server

uses tcmts.core.ts.compiler, which uses process

The checker's answers canonicalization needs, asked in batches: every identifier's symbol (and every shorthand property's value) in a few round trips to the compiler process, then every alias's target. Anything not asked ahead is asked when needed. Not a structural name's (b in a.b, a key, a member): canonicalization doesn't resolve those, and the checker would infer the type of what's left of them to answer. And the modules the files import from (a reference to an import is checked against its module), and what's asked later is kept too: a program's answers don't change.

import { isStructuralName } from "#codebase/tcmts/core/canonicalize";
import type { SymbolLookup } from "#codebase/tcmts/core/canonicalize";
import { compiler } from "#codebase/tcmts/core/ts";
import type { TypeChecker } from "#codebase/tcmts/core/ts";
import {
  isExportDeclaration as ts_isExportDeclaration, isImportDeclaration as ts_isImportDeclaration,
  SyntaxKind as ts_SyntaxKind,
} from "typescript/ast";
import type { Identifier as ts_Identifier, Node as ts_Node } from "typescript/ast";
import { SymbolFlags as ts_SymbolFlags } from "typescript/sync";
import type { Symbol as ts_Symbol } from "typescript/sync";

export function prefetched(checker: TypeChecker, roots: ts_Node[]): SymbolLookup {
  const ids: ts_Node[] = [];
  const shorthands: ts_Node[] = [];
  const walk = (n: ts_Node) => {
    if (n.kind === ts_SyntaxKind.Identifier) {
      if (!isStructuralName(n as ts_Identifier)) ids.push(n);
    } else if (n.kind === ts_SyntaxKind.ShorthandPropertyAssignment) shorthands.push(n);
    n.forEachChild(walk, (arr) => void arr.forEach(walk));
  };
  roots.forEach(walk);
  for (const sf of new Set(roots.map((r) => r.getSourceFile())))
    for (const st of sf.statements)
      if ((ts_isImportDeclaration(st) || ts_isExportDeclaration(st)) && st.moduleSpecifier)
        ids.push(st.moduleSpecifier);
  const api = compiler();
  const chunks = <T>(xs: T[], n = 4000) =>
    Array.from({ length: Math.ceil(xs.length / n) }, (_, i) => xs.slice(i * n, i * n + n));
  const symbols = new Map<ts_Node, ts_Symbol | undefined>();
  for (const c of chunks(ids))
    api
      .batch(...c.map((id) => checker.getSymbolAtLocation.gen(id)))
      .forEach((s, i) => symbols.set(c[i]!, s as ts_Symbol | undefined));
  const values = new Map<ts_Node, ts_Symbol | undefined>();
  for (const c of chunks(shorthands))
    api
      .batch(...c.map((n) => checker.getShorthandAssignmentValueSymbol.gen(n)))
      .forEach((s, i) => values.set(c[i]!, s as ts_Symbol | undefined));
  const aliased = new Map<ts_Symbol, ts_Symbol>();
  const aliases = [
    ...new Set(
      [...symbols.values(), ...values.values()].filter(
        (s): s is ts_Symbol => !!s && !!(s.flags & ts_SymbolFlags.Alias),
      ),
    ),
  ];
  for (const c of chunks(aliases))
    api
      .batch(...c.map((s) => checker.getAliasedSymbol.gen(s)))
      .forEach((t, i) => aliased.set(c[i]!, t as ts_Symbol));
  return {
    getSymbolAtLocation: (node) => {
      if (!symbols.has(node)) symbols.set(node, checker.getSymbolAtLocation(node));
      return symbols.get(node);
    },
    getShorthandAssignmentValueSymbol: (node) => {
      if (!values.has(node)) values.set(node, checker.getShorthandAssignmentValueSymbol(node));
      return values.get(node);
    },
    getAliasedSymbol: (sym) => {
      let t = aliased.get(sym);
      if (!t) aliased.set(sym, (t = checker.getAliasedSymbol(sym)));
      return t;
    },
  };
}

uses

used by