UNPKG

@ts-for-gir/lib

Version:

Typescript .d.ts generator from GIR for gjs

158 lines 7.52 kB
import { TypeIdentifier, ClosureType, BinaryType, PromiseType, VoidType, TupleType, BooleanType } from "../gir.js"; import { IntrospectedStaticClassFunction, IntrospectedConstructor, IntrospectedVirtualClassFunction } from "./function.js"; import { IntrospectedBaseClass, IntrospectedInterface } from "./class.js"; /** * Generates three overloaded function signatures for async methods: * 1. A Promise-based version without callback * 2. A callback-based version with required callback * 3. A union version with optional callback * * @param node - The original class function to create overloads from * @param async_parameters - Parameters for the Promise-based version (without callback) * @param sync_parameters - Parameters for the callback version * @param async_return - The Promise return type * @returns Array of three overloaded function signatures */ function generatePromisifyOverloadedSignatures(node, async_parameters, sync_parameters, async_return) { // Promise-based overload (without callback) const promiseOverload = node.copy({ parameters: async_parameters, returnType: async_return }); // Callback-based overload (with required callback) const callbackOverload = node.copy({ parameters: sync_parameters, returnType: VoidType }); // Union overload (with optional callback) const unionOverload = node.copy({ parameters: [...async_parameters, sync_parameters[sync_parameters.length - 1].copy({ isOptional: true })], returnType: new BinaryType(async_return, VoidType) }); return [promiseOverload, callbackOverload, unionOverload]; } /** * Searches for a corresponding finish method within a class * Matches either {name}_finish or {name without _async}_finish * * @param cls - The class to search in * @param node - The async function to find the finish method for * @returns The finish method if found, undefined otherwise */ function findFinishMethodInClass(cls, node) { const members = node instanceof IntrospectedStaticClassFunction ? [...cls.constructors, ...cls.members.filter(m => m instanceof IntrospectedStaticClassFunction)] : [...cls.members.filter(m => !(m instanceof IntrospectedStaticClassFunction))]; return members.find(m => m.name === `${node.name.replace(/_async$/, "")}_finish` || m.name === `${node.name}_finish`); } /** * Searches for a finish method in the class hierarchy: * 1. Searches in the current class * 2. Searches in the interface parent if available * 3. Searches through the parent class hierarchy * * @param node - The async function to find the finish method for * @param parent - The immediate parent class * @param interfaceParent - Optional interface parent to search in * @returns The finish method if found, undefined otherwise */ function findFinishMethod(node, parent, interfaceParent) { // Search in current class let async_res = findFinishMethodInClass(parent, node); // If not found and we have an interface parent, search there if (!async_res && interfaceParent) { async_res = findFinishMethodInClass(interfaceParent, node); } // If still not found, search through parent hierarchy if (!async_res) { async_res = parent.findParentMap(parentClass => findFinishMethodInClass(parentClass, node)); } return async_res; } /** * Creates a Promise return type for an async function * Handles both simple returns and tuple returns with output parameters * * @param async_res - The finish method or constructor to create the return type from * @returns A PromiseType containing the appropriate return type */ function createAsyncReturn(async_res) { const output_parameters = async_res instanceof IntrospectedConstructor ? [] : async_res.output_parameters; const return_type = async_res.return(); // For async methods that return multiple values (like read_bytes_finish) if (output_parameters.length > 0) { // If there's only one output parameter and the return type is void/boolean, // use the output parameter as the Promise result type if (output_parameters.length === 1 && (return_type.equals(VoidType) || return_type.equals(BooleanType))) { return new PromiseType(output_parameters[0].type); } // Special case: If return type is boolean and we have multiple output parameters, // GJS drops the boolean from the tuple (as it's used for error handling) if (return_type.equals(BooleanType) && output_parameters.length > 0) { const [firstParam, ...restParams] = output_parameters; return new PromiseType(new TupleType(firstParam.type, ...restParams.map(p => p.type))); } // Otherwise, return a tuple of [return_value, ...output_parameters] return new PromiseType(new TupleType(return_type, ...output_parameters.map(p => p.type))); } // For simple async methods that just return a value return new PromiseType(return_type); } /** * Checks if a parameter is a Gio.AsyncReadyCallback * * @param param - The function parameter to check * @returns true if the parameter is an AsyncReadyCallback, false otherwise */ function isAsyncReadyCallback(param) { const unwrapped = param.type.unwrap(); return (unwrapped instanceof ClosureType && unwrapped.type instanceof TypeIdentifier && unwrapped.type.is("Gio", "AsyncReadyCallback")); } /** * Checks if a function is an async function that can be promisified * Must have a corresponding _finish method and take an AsyncReadyCallback */ function isPromisifiable(node) { // Skip virtual functions if (node instanceof IntrospectedVirtualClassFunction) return false; // Must have at least one parameter if (node.parameters.length === 0) return false; // Last parameter must be AsyncReadyCallback const last_param = node.parameters[node.parameters.length - 1]; if (!last_param || !isAsyncReadyCallback(last_param)) return false; // Must have a parent class const parent = node.parent; if (!(parent instanceof IntrospectedBaseClass)) return false; // Must have a corresponding finish method const async_res = findFinishMethod(node, parent, node.interfaceParent instanceof IntrospectedInterface ? node.interfaceParent : undefined); return async_res !== undefined; } /** * Transforms async functions to include Promise-based overloads */ export function promisifyFunctions(functions) { return functions .map(node => { // Check if function can be promisified if (!isPromisifiable(node)) return node; const parent = node.parent; const async_res = findFinishMethod(node, parent, node.interfaceParent instanceof IntrospectedInterface ? node.interfaceParent : undefined); // Create parameters for Promise version (without callback) const async_parameters = node.parameters.slice(0, -1).map(p => p.copy({ parent: node })); // Create parameters for callback version (all parameters required) const sync_parameters = node.parameters.map(p => p.copy({ isOptional: false })); // Create Promise return type const async_return = createAsyncReturn(async_res); // Generate the three overloaded signatures return generatePromisifyOverloadedSignatures(node, async_parameters, sync_parameters, async_return); }) .flat(1); } //# sourceMappingURL=promisify.js.map