apiOf

tcmts.cli.package_pages.apiOf · #jungorud · term · runs on the server

uses node:path

The package's exports as its types declare them: the API a definition using it is checked against.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { clip, kindOf } from "#codebase/tcmts/cli/package_pages";
import type { ApiEntry } from "#codebase/tcmts/cli/package_pages";
import { highlight } from "#codebase/tcmts/cli/pages";
import { createProgram } from "#codebase/tcmts/core/program";
import { declarationOf } from "#codebase/tcmts/core/ts";
import path from "node:path";
import {
  ModuleKind as ts_ModuleKind, SymbolFlags as ts_SymbolFlags, TypeFormatFlags as ts_TypeFormatFlags,
} from "typescript/sync";
import type { Symbol as ts_Symbol } from "typescript/sync";

export function apiOf(cb: Codebase, alias: string): ApiEntry[] {
  // a file in the cache importing the package, never written: the package's types are what it resolves to
  const from = path.join(cb.lay.cacheDir, "__tcm_api__.ts");
  const { program, checker } = createProgram([from], { ...cb.compilerOptions(), noEmit: true, skipLibCheck: true }, new Map([[from, `import ${JSON.stringify(alias)};\n`]]));
  const file = program.getResolvedModule(from, alias, ts_ModuleKind.ESNext)?.resolvedFileName;
  const mod = file && checker.getSymbolOfSourceFile(file);
  if (!mod) return [];
  const flags = ts_TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | ts_TypeFormatFlags.WriteArrowStyleSignature;
  const entry = (name: string, sym: ts_Symbol): ApiEntry => {
    const s = sym.flags & ts_SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
    const kind = kindOf(s);
    let signature = "";
    try {
      // a type reads best as declared (its name alone says nothing)
      if (kind === "interface" || kind === "type" || kind === "enum") {
        const decl = declarationOf(s);
        signature = decl ? clip(decl.getText()) : "";
      } else if (kind !== "namespace") signature = checker.typeToString(checker.getTypeOfSymbol(s), undefined, flags);
    } catch {
      signature = "";
    }
    return { name, kind, signature, html: highlight(signature, [], new Set()), doc: s.getDocumentationComment(checker) };
  };
  const out = checker
    .getExportsOfModule(mod)
    .filter((s) => !s.name.startsWith("__"))
    .map((s) => entry(s.name, s));
  // `export = fn` (CommonJS style): the module itself is the API
  const exportEquals = mod.getExports().get("export=" as never);
  if (exportEquals && !out.some((e) => e.kind === "function" || e.kind === "class")) out.unshift(entry("default", exportEquals));
  return out.sort((a, b) => (a.name === "default" ? -1 : b.name === "default" ? 1 : a.name < b.name ? -1 : 1)).slice(0, 500);
}

uses

used by