UNPKG

@ts-for-gir/lib

Version:

Typescript .d.ts generator from GIR for gjs

188 lines (147 loc) 6 kB
import { DefaultFormatter } from "../formatters/default.ts"; import type { Formatter } from "../formatters/formatter.ts"; import { JSONFormatter } from "../formatters/json.ts"; import type { FormatGenerator } from "../generators/generator.ts"; import { generify } from "../generics/generify.ts"; import type { GirModule } from "../index.ts"; import { inject } from "../injections/inject.ts"; import type { GeneratorConstructor, OptionsGeneration, OptionsTransform } from "../types/index.ts"; import { TwoKeyMap } from "../util.ts"; import { ClassVisitor } from "../validators/class.ts"; import { InterfaceVisitor } from "../validators/interface.ts"; import type { GirVisitor } from "../visitor.ts"; import type { IntrospectedNamespace } from "./namespace.ts"; export class NSRegistry { mapping: TwoKeyMap<string, string, IntrospectedNamespace> = new TwoKeyMap(); private formatters: Map<string, Formatter> = new Map(); private generators: Map<string, GeneratorConstructor<unknown>> = new Map(); c_mapping: Map<string, { version: string; name: string }[]> = new Map(); transformations: GirVisitor[] = []; subtypes = new TwoKeyMap<string, string, TwoKeyMap<string, string, boolean>>(); constructor() { this.formatters.set("json", new JSONFormatter()); } registerTransformation(visitor: GirVisitor) { this.transformations.push(visitor); // Apply transformations to already built namespaces. this.mapping.forEach((n) => { n.accept(visitor); }); } registerFormatter(output: string, formatter: Formatter) { this.formatters.set(output, formatter); } getFormatter(output: string) { return this.formatters.get(output) ?? new DefaultFormatter(); } registerGenerator<T>( output: string, generator: { new (namespace: IntrospectedNamespace, options: OptionsGeneration): FormatGenerator<T> }, ) { this.generators.set(output, generator); } async getGenerator<T>(output: string): Promise<GeneratorConstructor<T> | undefined> { // Handle loading external generators... if (!this.generators.has(output)) { let Generator: { default: GeneratorConstructor<unknown> }; try { Generator = (await import(`@gi.ts/generator-${output}`)) as { default: GeneratorConstructor<unknown> }; if (Generator) { console.log(`Loading generator "@gi.ts/generator-${output}"...`); this.generators.set(output, Generator.default); return Generator.default as GeneratorConstructor<T>; } } catch { try { Generator = (await import(`gi-ts-generator-${output}`)) as { default: GeneratorConstructor<unknown>; }; console.log(`Loading generator "gi-ts-generator-${output}"...`); this.generators.set(output, Generator.default); return Generator.default as GeneratorConstructor<T>; } catch { try { Generator = (await import(`${output}`)) as { default: GeneratorConstructor<unknown> }; console.log(`Loading generator "${output}"...`); this.generators.set(output, Generator.default); return Generator.default as GeneratorConstructor<T>; } catch {} } } } return this.generators.get(output) as GeneratorConstructor<T> | undefined; } private _transformNamespace(namespace: IntrospectedNamespace) { this.transformations.forEach((t) => { namespace.accept(t); }); } namespace(name: string, version: string): IntrospectedNamespace | null { const namespace = this.mapping.get(name, version); return namespace ?? null; } namespacesForPrefix(c_prefix: string): IntrospectedNamespace[] { return (this.c_mapping.get(c_prefix) ?? []).map((c_mapping) => this.assertNamespace(c_mapping.name, c_mapping.version), ); } transform(options: OptionsTransform) { // In tolerant external-deps mode (with --allow-missing-deps) the core namespaces // may not be loaded. Sync their package_version only when actually present; // generify/inject still run on whatever IS loaded (other modules' transformations // don't depend on GLib being in the registry). const GLib = this.namespace("GLib", "2.0"); const Gio = this.namespace("Gio", "2.0"); const GObject = this.namespace("GObject", "2.0"); // These follow the GLib version. if (GLib && Gio) Gio.package_version = [...GLib.package_version]; if (GLib && GObject) GObject.package_version = [...GLib.package_version]; const interfaceVisitor = new InterfaceVisitor(); this.registerTransformation(interfaceVisitor); const classVisitor = new ClassVisitor(); this.registerTransformation(classVisitor); if (GLib && Gio) { console.log("Adding generics..."); generify(this, options.inferGenerics); console.log("Injecting types..."); inject(this); } } defaultVersionOf(name: string): string | null { // GJS has a hard dependency on these versions. if (name === "GLib" || name === "Gio" || name === "GObject") { return "2.0"; } const meta = this.mapping.getIfOnly(name); if (meta) { return meta[0]; } return null; } assertDefaultVersionOf(name: string): string { const version = this.defaultVersionOf(name); if (version) { return version; } // This mirrors GJS' and GI's default behavior. // If we can't find a single version of an unspecified dependency, we throw an error. throw new Error(`No single version found for unspecified dependency: ${JSON.stringify(name)}.`); } assertNamespace(name: string, version: string): IntrospectedNamespace { const namespace = this.mapping.get(name, version) ?? null; if (!namespace) { throw new Error(`Namespace '${name}' not found.`); } return namespace; } register(namespace: GirModule): IntrospectedNamespace { this.mapping.set(namespace.namespace, namespace.version, namespace); namespace.c_prefixes.forEach((c_prefix) => { const c_map = this.c_mapping.get(c_prefix) || []; c_map.push({ name: namespace.namespace, version: namespace.version }); this.c_mapping.set(c_prefix, c_map); }); this._transformNamespace(namespace); return namespace; } }